Install
$ agentstack add skill-efeumutaslan-sap-skills-sap-odata ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
OData Protocol for SAP — Design and Consumption
Related Skills
sap-rap-comprehensive— Primary way to build OData v4 services in modern ABAPsap-cap-advanced— Primary way to build OData v4 services in Node.js/Java on BTPsap-ui5-fiori— Biggest OData consumer in the SAP ecosystemsap-integration-suite-advanced— OData in iFlows, API Management, and B2B scenariossap-s4hana-extensibility— Released OData APIs vs custom OData services
Quick Start
Check a service's metadata (what it exposes):
GET /sap/opu/odata/sap/ZMY_SERVICE/$metadata # OData v2
GET /odata/v4/my-service/$metadata # OData v4
Query entities with common options:
GET /Products?$filter=Price gt 100 and Category eq 'Electronics'
&$select=ID,Name,Price
&$expand=Supplier($select=Name,Country)
&$orderby=Name asc
&$top=20&$skip=0
&$count=true
Pick v2 or v4:
| Decision | Choose | |----------|--------| | New greenfield backend | v4 | | Modern ABAP (RAP), CAP | v4 (RAP v4 is default since 7.54) | | Legacy SEGW in S/4 on-prem | v2 (some SAP standard services are still v2) | | Fiori Elements on S/4HANA 2023+ | v4 preferred | | UI5 consumer for legacy backend | v2 (with sap.ui.model.odata.v2.ODataModel) |
Core Concepts
v2 vs v4 — critical differences
| Feature | OData v2 | OData v4 | |---------|----------|----------| | Key syntax | /Products('HT-1001') | /Products(ID='HT-1001') or /Products('HT-1001') | | Filter on navigation | $filter=Supplier/Name eq 'X' | $filter=Supplier/Name eq 'X' (same) | | Count | /$count or $inlinecount=allpages | $count=true | | Batch format | multipart/mixed | application/json | | Function import | /GetProducts?name='X' | /getProducts(name='X') | | Action import | POST /SubmitOrder | POST /submitOrder | | Deep insert | Supported | Supported | | $apply | Not standard | Standard (aggregations) | | JSON format | Verbose / light | Standard JSON | | Type system | EDM types | EDM types (extended) |
Entity Data Model (EDM) types
| Type | SQL equivalent | Notes | |------|----------------|-------| | Edm.String | VARCHAR/NVARCHAR | MaxLength, Unicode facets | | Edm.Int32 | INT | 32-bit signed | | Edm.Int64 | BIGINT | 64-bit signed | | Edm.Decimal | DECIMAL | Precision, Scale facets | | Edm.Double | DOUBLE | 64-bit float | | Edm.Boolean | BOOLEAN | true/false | | Edm.DateTimeOffset | TIMESTAMP WITH TZ | ISO 8601 with offset | | Edm.Date | DATE | v4 only (v2 uses DateTime) | | Edm.TimeOfDay | TIME | v4 only | | Edm.Guid | UUID | Without hyphens in URLs | | Edm.Binary | BLOB | Base64 in JSON |
Service document structure
/ # Service document — lists entity sets
/$metadata # EDMX — complete service description
/Products # Entity set
/Products('HT-1001') # Entity
/Products('HT-1001')/Name # Primitive property
/Products('HT-1001')/Supplier # Navigation property
/Products/$count # v4: entity count
Common Patterns
Pattern 1: Query options reference
# Select only specific fields (reduces payload)
?$select=ID,Name,Price
# Include related entities (JOIN)
?$expand=Supplier,Category
?$expand=Supplier($select=Name,Country) # nested select
?$expand=Orders($filter=Status eq 'Open') # nested filter (v4)
# Filter
?$filter=Price gt 100
?$filter=contains(Name, 'Pro') # v4: contains, startswith, endswith
?$filter=substringof('Pro', Name) # v2: substringof
?$filter=Category eq 'Electronics' and Price le 500
?$filter=Date ge 2026-01-01 and Date lt 2026-02-01 # v4 date
?$filter=Supplier/Country eq 'DE' # navigation filter
# Sort
?$orderby=Name asc
?$orderby=Price desc, Name asc
# Pagination
?$top=20&$skip=40 # rows 41-60
# Count
?$count=true # v4
?$inlinecount=allpages # v2
# Search (v4, if service supports Capabilities.SearchRestrictions.Searchable)
?$search=laptop
# Aggregations (v4 with $apply)
?$apply=groupby((Category), aggregate(Price with sum as TotalPrice))
Pattern 2: CRUD operations
# CREATE
POST /Products
Content-Type: application/json
X-CSRF-Token:
{
"ID": "HT-9999",
"Name": "New Product",
"Price": 299.99
}
# READ
GET /Products('HT-1001')
# UPDATE (full replace)
PUT /Products('HT-1001')
Content-Type: application/json
If-Match: W/"etag-value"
{ "ID": "HT-1001", "Name": "Updated", "Price": 199.99 }
# PATCH (partial update) — preferred
PATCH /Products('HT-1001')
Content-Type: application/json
If-Match: W/"etag-value"
{ "Price": 199.99 }
# DELETE
DELETE /Products('HT-1001')
If-Match: W/"etag-value"
Pattern 3: Batch request (v4 JSON batch)
POST /$batch
Content-Type: application/json
{
"requests": [
{ "id": "1", "method": "GET", "url": "Products('HT-1001')" },
{ "id": "2", "method": "GET", "url": "Suppliers('S-001')" },
{
"id": "3",
"method": "PATCH",
"url": "Products('HT-1001')",
"headers": { "Content-Type": "application/json", "If-Match": "*" },
"body": { "Price": 199.99 },
"atomicityGroup": "tx1"
}
]
}
Pattern 4: CSRF token flow (write operations)
# Step 1: Fetch token via any GET
GET /sap/opu/odata/sap/ZMY_SERVICE/
X-CSRF-Token: Fetch
# Response headers include:
X-CSRF-Token: abc123xyz
Set-Cookie: sap-XSRF_...
# Step 2: Use token + cookie in subsequent writes
POST /Products
X-CSRF-Token: abc123xyz
Cookie: sap-XSRF_...
UI5's ODataModel handles this automatically. Custom fetch clients must do it manually.
Pattern 5: CAP OData service
// srv/products-service.cds
using { my.db.Products } from '../db/schema';
service ProductsService @(path: '/products') {
entity Products as projection on my.db.Products;
// Action
action approveProduct(ID: String) returns String;
// Function
function productsOverStock(threshold: Integer) returns array of Products;
}
Exposes GET /products/Products, POST /products/approveProduct, etc. — automatically OData v4.
Pattern 6: RAP projection as OData service
" Service definition (ZSD_PRODUCTS)
@EndUserText.label: 'Products Service'
define service ZSD_PRODUCTS {
expose ZC_PRODUCT as Products;
expose ZC_SUPPLIER as Suppliers;
}
" Service binding (ZSB_PRODUCTS_V4)
" Binding Type: OData V4 - UI
" Service Definition: ZSD_PRODUCTS
Published at /sap/opu/odata4/sap/zsb_products_v4/srvd_a2x/sap/zsd_products/0001/.
Pattern 7: UI5 consumption
// OData v4
const oModel = new sap.ui.model.odata.v4.ODataModel({
serviceUrl: "/odata/v4/products/",
synchronizationMode: "None",
operationMode: "Server",
autoExpandSelect: true
});
// Read
const oContext = oModel.bindContext("/Products('HT-1001')");
oContext.requestObject().then(oProduct => console.log(oProduct));
// Update (PATCH)
oContext.setProperty("Price", 199.99);
await oModel.submitBatch("$auto");
// Create
const oListBinding = oModel.bindList("/Products");
const oNewContext = oListBinding.create({ ID: "HT-9999", Name: "New" });
await oNewContext.created();
Pattern 8: External client (fetch/axios)
// Fetch CSRF token
const tokenRes = await fetch("/sap/opu/odata/sap/ZMY_SERVICE/", {
headers: { "X-CSRF-Token": "Fetch" },
credentials: "include"
});
const csrfToken = tokenRes.headers.get("x-csrf-token");
// Write with token
const createRes = await fetch("/sap/opu/odata/sap/ZMY_SERVICE/Products", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": csrfToken,
"Accept": "application/json"
},
body: JSON.stringify({ ID: "HT-9999", Name: "New" })
});
Error Catalog
| HTTP | Error | Cause | Fix | |------|-------|-------|-----| | 400 | Invalid filter expression | Wrong filter syntax or unknown property | Check property name case; eq/ne/gt/lt must be lowercase; strings need single quotes | | 400 | Could not parse URI | Invalid key syntax, wrong v2 vs v4 | v2: /Products('HT-1001'); v4: /Products(ID='HT-1001') or /Products('HT-1001') | | 401 | Authentication failed | Missing or invalid credentials | Add Basic Auth, form login, or OAuth token | | 403 | CSRF token validation failed | Missing CSRF token on write | Fetch token via GET with X-CSRF-Token: Fetch, reuse on POST/PUT/PATCH/DELETE | | 403 | Not authorized | User lacks permissions | Check role (PFCG for ABAP, XSUAA scopes for CAP/BTP) | | 404 | Resource not found | Wrong URL or service not active | Check service is published; verify path; check /$metadata loads | | 412 | Precondition Failed | Missing or stale If-Match etag | Include If-Match: from the last read | | 428 | Precondition Required | etag required but not sent | Send If-Match: * to force update without etag check (if allowed) | | 501 | Not Implemented | Function/feature not supported by service | Check $metadata — does the service declare the capability? | | 500 | /IWBEP/CX_MGW_BUSI_EXCEPTION | Business logic error in SEGW data provider | Read the inner message; check backend logs (ST22 for ABAP dumps) |
Performance Tips
- Always
$selectwhat you need — never fetch full entities when you only need 3 columns. $expandis expensive — prefer a second batch request over deep expands.- Server-side paging with
$top+$skip— never load all rows; clients like UI5 do this automatically withgrowing. $count=true— only request the count when the UI actually shows it.- Batch write operations — one
$batchinstead of N single calls reduces round-trip time dramatically. - Use
PATCH, notPUT— PATCH sends only changed fields, PUT sends the whole entity. - Index navigation property columns —
$filteron joined fields otherwise does full scans. $applyfor aggregations (v4) — let the server aggregate instead of fetching raw rows.- Cache
$metadata— it rarely changes; UI5 does this automatically. - Disable cursors for small result sets — OData v4
Capabilities.TopSupportedcan be tuned. - Projection CDS views — at the DB layer, expose only the fields needed by OData.
Gotchas
- Single quotes for strings, no quotes for numbers —
$filter=Name eq 'X'vs$filter=Price eq 100. - Date literals differ — v2:
datetime'2026-01-01T00:00:00'; v4:2026-01-01(Date) or2026-01-01T00:00:00Z(DateTimeOffset). - URL encoding —
$filter=Name eq 'O''Brien'(apostrophe doubled), spaces as%20. $inlinecountvs$count— don't mix up;$inlinecount=allpagesis v2,$count=trueis v4.- Navigation property case sensitivity —
$expand=Supplier≠$expand=supplierin most services. - Action names — v2 uses
GET+ function imports for side-effect-free,POSTotherwise; v4 clearly separates Actions (POST) from Functions (GET). - Deep insert depth — most backends limit to 2–3 levels.
- Batch changesets — atomic unit in v2
$batch; mistakes in one operation roll back the whole changeset. - Lambda operators (
any,all) — v4 only; v2 services ignore or error. - Key predicates with multiple fields —
/Orders(OrderID=5,ItemID=1)— order matters in some implementations.
Debugging Workflow
- Always check
$metadatafirst — it's the source of truth for what's exposed. - Use the Gateway Client (
/IWFND/GW_CLIENT) on ABAP — test requests with full headers/body. - Browser DevTools → Network → XHR filter — see the exact URL, headers, and response.
- Postman/Insomnia — build requests manually, inspect auth headers.
- Enable trace in backend — ABAP:
/IWFND/TRACES; CAP:DEBUG=odata*. - Metadata viewer tools — odata-debugger shows the service structure.
References
references/query-options-cheatsheet.md— All query options with examplesreferences/edm-types-reference.md— EDM type catalog with literals and facetsreferences/odata-v2-vs-v4.md— Side-by-side comparison for migrationtemplates/cap-service-template.cds— CAP OData service skeletontemplates/rap-service-definition.asddls— RAP service definition template
MCP Server Integration
For OData development:
@cap-js/mcp-server— CAP service design, OData metadata, CDS authoringmarianfoo/mcp-sap-docs— SAP documentation lookup (OData SDK, ABAP OData)- Vibing Steampunk MCP — ABAP Gateway service activation, SEGW operations
Install via mcp-configs/fullstack-btp.json.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: efeumutaslan
- Source: efeumutaslan/SAP-SKILLS
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.