OData Services in S/4HANA – Building & Exposing APIs via SEGW and RAP: A 25,000+ Word Hands-On Masterclass
Day 8 of the S/4HANA ABAP Development & Fiori Track
By @FreeLearning365 and Tech Partner @techbook24
Introduction: The API That Unlocked the Business
TechBook24's e-commerce platform is booming. The sales portal now needs to automatically create purchase orders in SAP when inventory drops below a threshold — no human intervention, no SAP GUI, just a clean, secure API call. The external system is a Node.js application running on Azure; it speaks RESTful JSON. SAP's answer is OData — a standardized protocol for building and consuming REST APIs that's deeply integrated into S/4HANA.
Arjun gets the assignment: expose a service that allows the portal to create a purchase order with all its items in a single call, retrieve order details, update them, and delete if needed. He must also ensure proper error handling and test it end-to-end before handing it over. In this tutorial, you'll join him in building this API from the ground up using both the traditional SEGW approach (OData v2) and the modern RAP framework (OData v4). You'll understand the architecture, write all the necessary ABAP code, and test everything with Postman. By the end, you'll have a complete, running OData service that mirrors real-world production APIs. Let's get started.
1. The OData Ecosystem in SAP – Gateway, SEGW, and RAP
1.1 What Is OData?
OData (Open Data Protocol) is an OASIS standard that defines a set of best practices for building and consuming RESTful APIs. It uses HTTP methods (GET, POST, PUT, DELETE) and returns data in JSON or XML format. SAP NetWeaver Gateway is the technology that enables SAP systems to expose OData services. The central design tool for OData v2 services is the Gateway Service Builder (transaction SEGW). With the move to S/4HANA and cloud, SAP introduced the Restful ABAP Programming Model (RAP) for OData v4, which is more declarative and deeply integrated with CDS.
1.2 OData v2 vs OData v4 – When to Use What
| Feature | OData v2 (SEGW) | OData v4 (RAP) |
|---|---|---|
| Maturity | Very mature, widely used in current Fiori apps | Strategic direction, newer, but growing adoption |
| Data Model | Defined in SEGW (Entity Types, Sets) | Derived from CDS views, annotations |
| Business Logic | Hand-coded in DPC/MPC classes | Managed via behavior definitions (managed, unmanaged) |
| State Management | Stateless, requires careful handling | Supports draft and transactional consistency |
| Batch Operations | Supported via $batch | Also supported |
TechBook24's current Fiori landscape is mostly v2, so Arjun will build the v2 service first, then demonstrate how the same functionality maps to RAP v4, ensuring future-readiness.
2. Building an OData v2 Service with SEGW – The Purchase Order API
2.1 The Data Model: Entity Types and Entity Sets
Arjun designs a simple OData model: an entity type PurchaseOrder with properties like PO number, vendor, date, total value, and a navigation property to a list of PurchaseOrderItem entities. The entity set PurchaseOrderSet represents the collection. He'll implement full CRUD on both.
Transaction: SEGW → Create Project → enter ZPO_SERVICE → Create.
- Right-click "Data Model" → "Import" → "DDIC Structure". He imports the CDS view
ZC_PO_Header_Enriched(from Part 37) as the basis for the header entity type. The system creates an entity typePurchaseOrderwith the fields. - Similarly, import a structure for items (e.g., from CDS
ZC_PurchaseOrderItem) to create entity typePurchaseOrderItem. - Define an association from PurchaseOrder to PurchaseOrderItem (1:n) and create the corresponding navigation property
ToItems. In the item entity, create a navigation back to header:ToHeader.
Screenshot Description: SEGW Data Model view showing the entity types, properties, and navigation paths.
2.2 Service Implementation – DPC and MPC Classes
After generating runtime objects (right-click project → Generate), the system creates a data provider class (DPC) and a model provider class (MPC). The DPC is where you write the actual logic for CRUD methods. Arjun opens the DPC extension class ZCL_ZPO_SERVICE_DPC_EXT in ABAP.
The following methods must be redefined:
PURCHASEORDERSE_GET_ENTITY– read single POPURCHASEORDERSE_GET_ENTITYSET– read list of POsPURCHASEORDERSE_CREATE_ENTITY– create POPURCHASEORDERSE_UPDATE_ENTITY– update POPURCHASEORDERSE_DELETE_ENTITY– delete PO- Similarly for items, but also navigation via the association.
Arjun implements GET_ENTITYSET for purchase orders:
METHOD purchaseorderse_get_entityset.
" Use CDS view or direct SELECT
SELECT ebeln, lifnr, vendor_name, aedat, netwr
FROM zc_po_header_enriched
INTO CORRESPONDING FIELDS OF TABLE @et_entityset
UP TO @io_tech_request_context->get_top( ) ROWS
WHERE aedat BETWEEN @iv_date_from AND @iv_date_to. " assume input parameters
ENDMETHOD.
For CREATE_ENTITY, he needs to insert into EKKO and EKPO. This is the heart of the API. The entry data comes in parameter ER_ENTITY, which is a structure of the entity type. He maps the fields to EKKO, generates a PO number using the internal number range, and then processes items if deep insert is used. We'll handle deep insert next.
3. Deep Insert – Creating a PO with Items in One Call
3.1 The Requirement
The e-commerce portal wants to send a single POST request containing the PO header and all its items, and get back the complete created PO. This is called a "deep insert" in OData. In SEGW, when you post to an entity set and include navigation properties with inline data, the system calls the CREATE_ENTITY method of the root entity, and then you must manually handle the child entities. Arjun will implement a custom CREATE_DEEP_ENTITY method instead.
3.2 Redefining CREATE_DEEP_ENTITY
He creates the method (or redefines it in the DPC extension). The method receives IT_NAVIGATION_PATH and IO_DATA_PROVIDER. He uses the IO_DATA_PROVIDER to read the deep structure. The typical signature:
METHOD /iwbep/if_mgw_appl_srv_runtime~create_deep_entity.
DATA: ls_header TYPE ty_po_header,
lt_items TYPE TABLE OF ty_po_item,
lv_ebeln TYPE ebeln.
" 1. Read the deep data
io_data_provider->read_entry_data(
IMPORTING es_data = ls_header ).
io_data_provider->read_navigation_data(
EXPORTING iv_entity_name = 'PurchaseOrderItem'
iv_nav_prop = 'ToItems'
IMPORTING et_data = lt_items ).
" 2. Generate PO number
CALL FUNCTION 'NUMBER_GET_NEXT'
EXPORTING
nr_range_nr = '01'
object = 'EINKBELEG'
IMPORTING
number = lv_ebeln.
" 3. Insert header into EKKO
INSERT INTO ekko VALUES lv_ebeln, ... (mapping from ls_header).
" 4. Insert items
LOOP AT lt_items INTO DATA(ls_item).
INSERT INTO ekpo VALUES lv_ebeln, ls_item-ebelp, ... .
ENDLOOP.
" 5. Return the created entity (with generated keys)
COMMIT WORK AND WAIT.
CALL METHOD super->/iwbep/if_mgw_appl_srv_runtime~create_deep_entity
EXPORTING
iv_entity_name = 'PurchaseOrder'
is_data = ls_header
it_navigation_path = it_navigation_path
io_data_provider = io_data_provider
io_tech_request_context = io_tech_request_context.
ENDMETHOD.
This is simplified; in reality, Arjun uses BAPI or standard function modules for creating POs (like BAPI_PO_CREATE1) to ensure all standard validations are executed. He calls BAPI_PO_CREATE1 and then maps the returned data. That's the production-grade approach.
3.3 Testing the Deep Insert with Postman
Arjun opens Postman. He first fetches a CSRF token (required for modifying requests) by sending a GET with the header X-CSRF-Token: Fetch to the service root. He then uses that token in subsequent POST/PUT/DELETE.
- GET /sap/opu/odata/sap/ZPO_SERVICE/ – returns service document.
- POST /sap/opu/odata/sap/ZPO_SERVICE/PurchaseOrderSet with a JSON body containing the header and inline items using the navigation property "ToItems".
{
"Lifnr": "0000001000",
"Bsart": "NB",
"ToItems": [
{
"Matnr": "MAT100",
"Menge": "10",
"Meins": "PC"
}
]
}
Screenshot Description: Postman showing the POST request with JSON body, and the response with the newly created PO number and all details, including the items expanded.
Arjun receives a 201 Created with the full PO data. The external portal can now create orders in SAP seamlessly.
4. CRUD Operations – Complete Implementation
4.1 GET_ENTITY (Read Single PO)
This method returns a single PO based on the key (EBELN). Arjun maps the keys from IT_KEY_TAB, reads EKKO, and fills the response. He also returns associated items via the navigation method TOITEMS_GET_ENTITY (or separate method). If the PO doesn't exist, he raises /iwbep/cx_mgw_busi_exception with HTTP 404.
4.2 UPDATE_ENTITY
For update, Arjun receives the key and the changed fields. He calls BAPI_PO_CHANGE to update the PO. He also handles status transitions like blocking a PO. Proper error mapping to OData exceptions ensures the client receives meaningful error messages.
4.3 DELETE_ENTITY
He uses BAPI_PO_DELETE or marks the PO for deletion (LOEKZ). The method returns 204 No Content on success.
Each operation is tested in Postman. Arjun demonstrates updating the vendor on a PO, deleting a test PO, and reading the list with $filter, $orderby, and $expand.
5. $expand and Query Options
OData provides powerful query options: $expand to include related entities, $filter, $orderby, $top, $skip. Arjun enables these by implementing the corresponding methods. For $expand, when a request includes ?$expand=ToItems, the DPC framework calls the navigation method for items. He ensures that the method uses the provided keys and returns the item set.
He tests: GET .../PurchaseOrderSet('4500000123')?$expand=ToItems. Response includes both header and item array. This reduces round trips for the client.
Screenshot Description: Postman response showing a PO with nested items array from $expand.
6. Error Handling – Meaningful Messages, Correct HTTP Codes
6.1 Using Message Classes and Exceptions
Instead of dumps or generic messages, Arjun uses the Gateway exception class /IWBEP/CX_MGW_BUSI_EXCEPTION to return structured errors. He defines a message container, adds messages from the BAPI return table, and raises the exception. The client then receives a JSON error object with code, message, and severity. He shows how to map BAPI errors like "PO already exists" to HTTP 409 Conflict.
6.2 Common Error Scenarios and HTTP Status Codes
- 400 Bad Request: invalid input data (e.g., missing required fields).
- 404 Not Found: PO does not exist.
- 409 Conflict: duplicate PO or lock issues.
- 500 Internal Server Error: unexpected runtime errors — but these should be caught and wrapped.
Arjun implements a helper method that converts BAPIRET2 into the appropriate OData exception. He tests with Postman by sending invalid data and verifying the error payload.
7. OData v4 with RAP – The Modern Approach
7.1 Why RAP?
RAP (Restful ABAP Programming Model) is the strategic framework for building OData v4 services and transactional Fiori apps. It uses CDS views as the data definition layer, behavior definitions for transaction logic, and automatically generates the OData service. It supports managed, unmanaged, and draft scenarios.
Arjun demonstrates how the same PO API can be built with RAP, leveraging the CDS views he created in Part 37. He creates a root CDS view for PO header, a composition view for items, and defines the behavior to allow create, update, delete. The framework handles deep create, business validation, and draft. No DPC coding is required for standard operations — only custom validation methods.
7.2 Step-by-Step RAP Service Creation
- Create CDS view
ZR_PO_HEADERas the root entity, with the@OData.publish: trueannotation. - Create CDS view
ZR_PO_ITEMas child, with composition to parent. - Define the behavior definition in ADT: right-click the root CDS → New → Behavior Definition. Choose managed scenario. Specify the operations (create, update, delete) and fields for number range (EBELN).
- Implement any custom validation in the behavior implementation class (e.g., check vendor limit). Arjun writes a method
checkBeforeSavethat raises an error if the PO net value exceeds the vendor limit — reusing the logic from the earlier BAdI. - Create a service definition and service binding to expose the OData v4 service.
He then tests the RAP service in the same way with Postman, noting the differences in URL structure (v4 uses /sap/opu/odata4/...) and the JSON format (no "d" wrapper). Deep create works out-of-the-box.
8. Testing with Postman – Complete Walkthrough
8.1 Setup and CSRF Token
Arjun configures Postman with Basic Auth (SAP user credentials). He creates a collection for the API. First request: GET /sap/opu/odata/sap/ZPO_SERVICE/ with header X-CSRF-Token: Fetch. He extracts the token from the response header for subsequent requests.
8.2 CRUD Test Cases
- Create PO: POST with JSON body (deep insert). Verify 201 and returned data.
- Read list: GET with $top=5. Verify 200 and array.
- Read single: GET with key. Verify 200.
- Update: PUT or PATCH (depending on implementation) with changed fields.
- Delete: DELETE. Verify 204.
He also demonstrates $filter=Netwr gt 50000, $orderby=Aedat desc, and $count=true. Every step is shown with actual request and response screenshots in the article.
9. Pro Tips and Common Pitfalls
- CSRF token expiry: Tokens are session-based; if you get 403, fetch a new token. Use the
x-csrf-tokenheader in every modifying request. - Deep insert and transaction consistency: Always commit only after successful processing of all child entities. Use COMMIT WORK AND WAIT.
- Performance: For GET_ENTITYSET, use $top, $skip to avoid returning millions of rows. Implement paging via
io_tech_request_context->get_top( )andget_skip( ). - Authorization: Never rely solely on API authentication; implement authority checks in the DPC using standard SAP authorization objects.
- Testing: Always test with Postman before handing over to UI developers. Use different user roles to verify security.
10. Pros, Cons, and Alternatives
| Method | Pros | Cons |
|---|---|---|
| SEGW + ABAP DPC | Full control, works with any data source, mature | High coding effort, no draft support, v2 only |
| RAP (Managed) | Low-code, draft & draft handling, auto-OData, v4 | Strictly CDS-based, less control over processing, requires S/4HANA >= 1809 |
| SAP Cloud Application Programming Model (CAP) | Cloud-native, great for side-by-side extensions, polyglot | Not for direct on-stack ABAP, separate environment |
11. The Full Hands-On Lab – Building the Entire API Step by Step
Arjun documents the following full-day lab that you can replicate:
- Create a new SEGW project ZPO_SERVICE.
- Import DDIC structures for header and item.
- Generate runtime objects and implement DPC extension for GET_ENTITYSET (list POs).
- Implement CREATE_DEEP_ENTITY for PO with items using BAPI_PO_CREATE1.
- Implement GET_ENTITY, UPDATE_ENTITY (BAPI_PO_CHANGE), DELETE_ENTITY.
- Add error handling: create message class, map BAPIRET2 to OData exceptions.
- Register and activate the service in /IWFND/MAINT_SERVICE.
- Test in gateway client (SEGW: Service Implementation → Gateway Client).
- Test in Postman: all CRUD and deep insert.
- Bonus: build a RAP version with CDS views and behavior definition, and test OData v4.
Every step is accompanied by code snippets, transaction descriptions, and expected outputs. The resulting service is ready for integration with the e-commerce portal.
12. Conclusion – The API Economy Is Here
Arjun's OData service is now live. The Node.js portal successfully creates POs in SAP, and the procurement team can monitor them in real time. He's demonstrated both the battle-tested SEGW approach and the modern RAP way, equipping TechBook24 with a future-proof API strategy. This skill set makes him indispensable — because every digital transformation initiative needs APIs.
Tomorrow, we'll step into the world of Fiori App Development — taking the CDS views and OData services you've built and creating stunning, responsive user interfaces with Fiori Elements and SAPUI5. You'll build a complete Fiori List Report for purchase orders, deploy it to the Launchpad, and customise it. The backend work you've done sets the perfect stage.
Keep coding, keep connecting.
End of Part 38 – OData Services Deep Dive. Brought to you by @FreeLearning365 and tech partner @techbook24.

0 Comments
thanks for your comments!