OData V4 Part 4: Actions, Functions and Batch Processing
Call RAP business operations over OData V4: bound and unbound actions, actions with parameters, functions, draft actions, and $batch with change sets for atomic multi-step requests.
Explanation
📌 KEY POINTS
✅ Actions change data → POST; functions only read → GET.
✅ Bound actions are called on an entity: …/PurchaseRequest(…)/<namespace>.approve.
✅ $batch bundles requests; a change set is processed atomically (all or nothing).
💡 The exact action name including namespace is in $metadata — copy it from there.
━━━━━━━━━━━━━━━━━━━━
▶ WHERE WE ARE
Parts 1–3 covered reading and CRUD. Business APIs need commands (approve, reject, copy) and multi-step requests. This part shows how RAP operations appear in OData V4.
━━━━━━━━━━━━━━━━━━━━
▶ 1. ACTIONS (CHANGE DATA)
- Declared in the BDEF and used in the projection BDEF ("use action approve;").
- Bound action (instance action): POST on the entity, followed by the qualified action name. In RAP V4 services the name is namespaced, e.g. com.sap.gateway.srvd.zui_purchreq.v0001.approve — check $metadata.
- Result: the returned entity (for "result [1] $self") in the response body.
- Static actions are bound to the entity collection: POST …/PurchaseRequest/<namespace>.createFromTemplate.
- Factory actions return the created instance.
━━━━━━━━━━━━━━━━━━━━
▶ 2. ACTIONS WITH PARAMETERS
Parameters from the abstract entity (e.g. ZD_RejectReason) are sent as JSON body: { "Reason": "Budget exceeded" }.
━━━━━━━━━━━━━━━━━━━━
▶ 3. FUNCTIONS (READ ONLY)
Declared as "function" in the BDEF; called with GET and parameters in parentheses: GET …/PurchaseRequest(…)/<namespace>.getApprovalLevel().
━━━━━━━━━━━━━━━━━━━━
▶ 4. DRAFT ACTIONS OVER ODATA
For draft-enabled UI services:
- Edit: POST on the active instance …(IsActiveEntity=true)/<ns>.Edit with { "PreserveChanges": true } → returns the draft.
- Prepare and Activate: POST on the draft instance …(IsActiveEntity=false)/<ns>.Prepare / <ns>.Activate.
- Discard: DELETE on the draft instance.
Fiori Elements does all of this automatically; you need it for tests and custom clients.\n\n━━━━━━━━━━━━━━━━━━━━\n▶ 5. $BATCH AND CHANGE SETS\nPOST …/$batch sends several requests in one HTTP call.
- Requests in the same change set (atomicityGroup in JSON batch) are executed in one RAP transaction: either all succeed or none is saved.
- Read requests are outside change sets.
- Fiori Elements sends almost everything via $batch.
- Use change sets when a client must create a header and then call an action on it atomically; alternatively use a deep create plus a separate action call.
⚠️ WATCH OUT: an error in one change set rolls back that change set only; other change sets in the same batch can still succeed.
━━━━━━━━━━━━━━━━━━━━
▶ 6. RESPONSES AND ERRORS
- 200/201 with the result, or 204 without content.
- Business errors from RAP (FAILED/REPORTED) arrive as 4xx with an error object; messages are in "error.details" (Part 5).
━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP
- Look up the action and function names in $metadata.
- Call approve on a submitted request (active instance).
- Call reject with a reason.
- Build a JSON batch with one change set: create request + create item (code example).
✅ CHECKPOINT: you can call every RAP operation over HTTP.
Code example
### {root} = /sap/opu/odata4/sap/zui_purchreq_o4/srvd/sap/zui_purchreq/0001### {ns} = com.sap.gateway.srvd.zui_purchreq.v0001 (copy exact name from $metadata) ### 1. Bound instance action (active instance)POST {root}/PurchaseRequest(RequestUUID=4c2a…,IsActiveEntity=true)/{ns}.approveContent-Type: application/jsonX-CSRF-Token: <token> {}# 200 OK -> the approved PurchaseRequest (result [1] $self) ### 2. Action with parameter (abstract entity ZD_RejectReason)POST {root}/PurchaseRequest(RequestUUID=4c2a…,IsActiveEntity=true)/{ns}.rejectContent-Type: application/jsonX-CSRF-Token: <token> { "Reason": "Budget exceeded" } ### 3. Function (read only)GET {root}/PurchaseRequest(RequestUUID=4c2a…,IsActiveEntity=true)/{ns}.getApprovalLevel() ### 4. Draft actionsPOST {root}/PurchaseRequest(RequestUUID=4c2a…,IsActiveEntity=true)/{ns}.Edit{ "PreserveChanges": true } # -> draftPOST {root}/PurchaseRequest(RequestUUID=4c2a…,IsActiveEntity=false)/{ns}.PreparePOST {root}/PurchaseRequest(RequestUUID=4c2a…,IsActiveEntity=false)/{ns}.ActivateDELETE {root}/PurchaseRequest(RequestUUID=4c2a…,IsActiveEntity=false) # discard ### 5. JSON $batch with one atomic change set (Web API service, no draft)POST {apiroot}/$batchContent-Type: application/jsonX-CSRF-Token: <token> { "requests": [ { "id": "1", "atomicityGroup": "g1", "method": "POST", "url": "PurchaseRequest", "headers": { "content-type": "application/json" }, "body": { "Description": "Laptops", "CurrencyCode": "EUR" } }, { "id": "2", "atomicityGroup": "g1", "dependsOn": ["1"], "method": "POST", "url": "$1/_Item", "headers": { "content-type": "application/json" }, "body": { "ItemNo": "0010", "Quantity": 3, "Price": 1200 } } ]}# g1 succeeds completely or not at allReal project scenario
A partner system created purchase requests and then called approve in two separate HTTP calls. When approve failed (e.g. missing budget), the request remained unapproved, and the partner created a new one on retry — producing duplicates.
The team changed the integration: the partner sends a JSON $batch with one change set (create + approve via a dependent request), or uses a deep create followed by a status check. Failed approvals now roll back the creation, and retries no longer create duplicates.
Common mistakes
- Calling actions with GET or functions with POST.
- Guessing the action name instead of copying the namespaced name from $metadata.
- Calling draft actions on the wrong instance (active vs draft).
- Expecting all change sets in a batch to be one transaction.
- Ignoring error details returned for failed actions.
- Using UI (draft) services for integrations that need atomic create-and-act.
Best practices
- Read action/function names and parameters from $metadata.
- Use change sets when several steps must be atomic.
- Return results from actions so clients need no extra read.
- Use Web API services without draft for integrations.
- Keep request collections (Postman/REST client) under version control.
Interview angle
Questions: "How is a RAP action called over OData V4?", "Action vs function?", "What is a change set in $batch?"
Strong answer: bound actions via POST on the entity URL with the namespaced action name (from $metadata), parameters as JSON body; functions via GET; draft actions Edit/Prepare/Activate and DELETE for discard; $batch bundles requests, change sets (atomicityGroup) run as one RAP transaction — all or nothing — while separate change sets are independent.
Practice this lesson
Quick check
1. How is a bound RAP action such as approve called in OData V4?
2. A client sends three PATCH calls and an approve action that must succeed or fail together. What should it use?
3. Which URL segment identifies an OData V4 Web API binding in RAP?
Interview questions
1. Which design rules do you apply to OData V4 APIs built with RAP?
Answer it in your own words first, then compare.
2. How are RAP actions, functions and draft actions called over OData V4, and when do you use $batch change sets?
Answer it in your own words first, then compare.
Reference-verified: the steps are checked against official SAP documentation and tutorials, and object and field names in the sample code are checked for consistency. The code has not been compiled or run on an SAP system by ERPClimb. Syntax marked release-dependent varies by SAP release, so check it in your own system.
More lessons in this topic
- OData V4 Part 1: The OData V4 Contract — Metadata, Keys, Navigation, Queries
- OData V4 Part 2: Publishing a RAP Service with an OData V4 Binding
- OData V4 Part 3: Test CRUD Requests, Payloads and Responses
- OData V4 Part 5: Troubleshooting HTTP Errors, Metadata and Payloads
- OData V4 Part 6: ETags and Optimistic Concurrency