https://raw.githubusercontent.com/ajmaradiaga/feeds/main/scmt/topics/SAP-BTP-Kyma-runtime-blog-posts.xmlSAP Community - SAP BTP, Kyma runtime2026-07-24T20:00:21.999755+00:00python-feedgenSAP BTP, Kyma runtime blog posts in SAP Communityhttps://community.sap.com/t5/technology-blog-posts-by-sap/sap-job-scheduling-service-rest-api-now-on-business-accelerator-hub-explore/ba-p/14341463SAP Job Scheduling Service REST API Now on Business Accelerator Hub - Explore and Try It Out!2026-03-05T07:00:00.029000+01:00DenisDuevhttps://community.sap.com/t5/user/viewprofilepage/user-id/180332<DIV class=""><H1 id="toc-hId-1662258789"><span class="lia-unicode-emoji" title=":rocket:">š</span> SAP Job Scheduling Service REST API Now on Business Accelerator Hub - Explore and Try It Out!</H1></DIV><P><STRONG>Big news for SAP developers!</STRONG><SPAN> </SPAN>The SAP Job Scheduling Service REST API is now available on the<SPAN> </SPAN><A href="https://api.sap.com/" target="_blank" rel="noopener noreferrer">SAP Business Accelerator Hub</A><SPAN> </SPAN>(API Hub for short), making it easier than ever to discover, explore, and integrate job scheduling capabilities into your SAP BTP applications.</P><P>Whether you're building Cloud Foundry or Kyma applications, automating workflows, or managing background tasks, you can now find everything you need in one centralized location alongside other SAP APIs.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="hero.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/379676i3A8B4E4582661446/image-size/large?v=v2&px=999" role="button" title="hero.png" alt="hero.png" /></span></P><DIV class=""><H2 id="toc-hId-1594828003"><span class="lia-unicode-emoji" title=":direct_hit:">šÆ</span> What's New?</H2></DIV><P>As of<SPAN> </SPAN><STRONG>February 18, 2026</STRONG>, the SAP Job Scheduling Service REST API is live on Business Accelerator Hub<SPAN> </SPAN><A href="https://api.sap.com/" target="_blank" rel="noopener noreferrer">https://api.sap.com/</A></P><DIV class=""><H3 id="toc-hId-1527397217">Why This Matters</H3></DIV><P>Before this release, developers had to piece together API documentation from help portals, blog posts, and code examples. Now, everything is in one place with:</P><UL><LI><span class="lia-unicode-emoji" title=":white_heavy_check_mark:">ā </span><SPAN> </SPAN><STRONG>Centralized Discovery</STRONG><SPAN> </SPAN>- Find the Job Scheduling Service API alongside 1000+ other SAP APIs</LI><LI><span class="lia-unicode-emoji" title=":white_heavy_check_mark:">ā </span><SPAN> </SPAN><STRONG>Interactive Testing</STRONG><SPAN> </SPAN>- Try API endpoints directly in your browser with the "Try It Out" feature</LI><LI><span class="lia-unicode-emoji" title=":white_heavy_check_mark:">ā </span><SPAN> </SPAN><STRONG>OpenAPI 3.0.3 Specification</STRONG><SPAN> </SPAN>- Standards-based documentation that works with any OpenAPI tool</LI><LI><span class="lia-unicode-emoji" title=":white_heavy_check_mark:">ā </span><SPAN> </SPAN><STRONG>Code Generation</STRONG><SPAN> </SPAN>- Generate client libraries in your favorite programming language</LI><LI><span class="lia-unicode-emoji" title=":white_heavy_check_mark:">ā </span><SPAN> </SPAN><STRONG>Live Documentation</STRONG><SPAN> </SPAN>- Always up-to-date API reference with request/response examples</LI></UL><HR /><DIV class=""><H2 id="toc-hId-1201800993"><span class="lia-unicode-emoji" title=":magnifying_glass_tilted_left:">š</span> What Can the Job Scheduling Service API Do?</H2></DIV><P>The Job Scheduling Service REST API provides complete programmatic control over job scheduling on SAP BTP:</P><UL><LI><span class="lia-unicode-emoji" title=":clipboard:">š</span> Job Management</LI><LI><span class="lia-unicode-emoji" title=":one_o_clock:">š</span> Schedule Management</LI><LI><span class="lia-unicode-emoji" title=":bar_chart:">š</span> Monitoring & Run Logs</LI><LI><span class="lia-unicode-emoji" title=":locked_with_key:">š</span> Authentication</LI></UL><HR /><DIV class=""><H2 id="toc-hId-1005287488"><span class="lia-unicode-emoji" title=":magnifying_glass_tilted_right:">š</span> How to Find the API on Business Accelerator Hub</H2></DIV><P>Let's walk through discovering the Job Scheduling Service API on BAH:</P><DIV class=""><H3 id="toc-hId-937856702">Option 1: Search for "Job Scheduling service"</H3></DIV><P>Search for "SAP Job Scheduling Service" directly in the search bar.</P><DIV class=""> </DIV><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="bah-search.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/379680i9C4F8679A402F450/image-size/large?v=v2&px=999" role="button" title="bah-search.png" alt="bah-search.png" /></span></P><DIV class=""><H3 id="toc-hId-741343197">Option 2: Direct Link</H3></DIV><P>Directly go to<SPAN> </SPAN><A href="https://api.sap.com/api/sap-btpjss-admin-v1/overview" target="_blank" rel="noopener noreferrer">https://api.sap.com/api/sap-btpjss-admin-v1/overview</A></P><DIV class=""> </DIV><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="bah-overview.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/379679i14B55CFCDFD6F61F/image-size/large?v=v2&px=999" role="button" title="bah-overview.png" alt="bah-overview.png" /></span></P><DIV class=""><H3 id="toc-hId-544829692">API Details Page</H3></DIV><P>You'll see:</P><UL><LI><STRONG>Overview</STRONG><SPAN> </SPAN>- High-level description of the API capabilities</LI><LI><STRONG>API Reference</STRONG><SPAN> </SPAN>- Complete list of all endpoints organized by category</LI><LI><STRONG>Schema View</STRONG><SPAN> </SPAN>- Detailed definitions of request and response objects</LI><LI><STRONG>Try It Out</STRONG><SPAN> </SPAN>- Interactive testing of API endpoints with real authentication</LI><LI><STRONG>Documents</STRONG><SPAN> </SPAN>- Additional documentation links</LI></UL><HR /><DIV class=""><H2 id="toc-hId-219233468"><span class="lia-unicode-emoji" title=":open_book:">š</span> Exploring the API Documentation</H2></DIV><P>The API documentation on BAH is organized into clear sections:</P><DIV class=""><H3 id="toc-hId-151802682">API Endpoints Overview</H3></DIV><P>The Job Scheduling Service API groups endpoints into three main categories:</P><P><STRONG><span class="lia-unicode-emoji" title=":wrench:">š§</span> Jobs</STRONG><SPAN> </SPAN>(<CODE>/scheduler/jobs</CODE>)</P><UL><LI><CODE>POST /jobs</CODE><SPAN> </SPAN>- Create a new job</LI><LI><CODE>GET /jobs</CODE><SPAN> </SPAN>- Retrieve all jobs</LI><LI><CODE>GET /jobs/{jobId}</CODE><SPAN> </SPAN>- Get details of a specific job</LI><LI><CODE>PATCH /jobs/{jobId}</CODE><SPAN> </SPAN>- Update an existing job</LI><LI><CODE>DELETE /jobs/{jobId}</CODE><SPAN> </SPAN>- Delete a job</LI></UL><P><STRONG><span class="lia-unicode-emoji" title=":calendar:">š </span> Schedules</STRONG><SPAN> </SPAN>(<CODE>/scheduler/jobs/{jobId}/schedules</CODE>)</P><UL><LI><CODE>POST /jobs/{jobId}/schedules</CODE><SPAN> </SPAN>- Create a schedule for a job</LI><LI><CODE>GET /jobs/{jobId}/schedules</CODE><SPAN> </SPAN>- Get all schedules for a job</LI><LI><CODE>GET /jobs/{jobId}/schedules/{scheduleId}</CODE><SPAN> </SPAN>- Get a specific schedule</LI><LI><CODE>PATCH /jobs/{jobId}/schedules/{scheduleId}</CODE><SPAN> </SPAN>- Update a schedule</LI><LI><CODE>DELETE /jobs/{jobId}/schedules</CODE><SPAN> </SPAN>- Delete all schedules</LI><LI><CODE>DELETE /jobs/{jobId}/schedules/{scheduleId}</CODE><SPAN> </SPAN>- Delete a specific schedule</LI></UL><P><STRONG><span class="lia-unicode-emoji" title=":bar_chart:">š</span> Run Logs</STRONG><SPAN> </SPAN>(<CODE>/scheduler/jobs/{jobId}/schedules/{scheduleId}/runs</CODE>)</P><UL><LI><CODE>GET /jobs/{jobId}/schedules/{scheduleId}/runs</CODE><SPAN> </SPAN>- Get execution logs</LI><LI><CODE>GET /jobs/{jobId}/schedules/{scheduleId}/runs/{runId}</CODE><SPAN> </SPAN>- Get details of a specific run</LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="bah-api-reference.gif" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/379677i60A637433C80B11C/image-size/large?v=v2&px=999" role="button" title="bah-api-reference.gif" alt="bah-api-reference.gif" /></span></P><DIV class=""><H3 id="toc-hId--119942192">Schema Definitions</H3></DIV><P>The API documentation includes detailed schema definitions for all request and response objects:</P><UL><LI><STRONG>CreateJobRequest</STRONG><SPAN> </SPAN>- Job creation with schedules</LI><LI><STRONG>JobDetails</STRONG><SPAN> </SPAN>- Complete job information</LI><LI><STRONG>ScheduleDetails</STRONG><SPAN> </SPAN>- Schedule configuration (cron, time, repeatInterval)</LI><LI><STRONG>RunLog</STRONG><SPAN> </SPAN>- Execution details, status, and HTTP responses</LI><LI><STRONG>Error responses</STRONG><SPAN> </SPAN>- Standard error format with messages and HTTP status codes</LI></UL><P>Each schema shows:</P><UL><LI>Property names and types</LI><LI>Required vs. optional fields</LI><LI>Default values</LI><LI>Validation constraints (min/max length, patterns, enums)</LI><LI>Example values</LI></UL><HR /><DIV class=""><H2 id="toc-hId--23052690"><span class="lia-unicode-emoji" title=":video_game:">š®</span> Try It Out - Test the API Interactively</H2></DIV><P>One of the most powerful features of Business Accelerator Hub is the<SPAN> </SPAN><STRONG>"Try It Out"</STRONG><SPAN> </SPAN>functionality. Let's walk through testing a real API endpoint!</P><DIV class=""><H3 id="toc-hId--512969202">Prerequisites</H3></DIV><P>Before you can test the API, you'll need:</P><OL><LI><span class="lia-unicode-emoji" title=":white_heavy_check_mark:">ā </span> A<SPAN> </SPAN><STRONG>Job Scheduling service instance</STRONG><SPAN> </SPAN>on SAP BTP (Trial or Live)</LI><LI><span class="lia-unicode-emoji" title=":white_heavy_check_mark:">ā </span> A<SPAN> </SPAN><STRONG>service key</STRONG><SPAN> </SPAN>with OAuth credentials</LI></OL><P>Don't have these yet? Check out our<SPAN> </SPAN><A href="https://community.sap.com/t5/technology-blogs-by-sap/job-scheduler-in-sap-business-technology-platform-overview-of-blog-posts/ba-p/13510707" target="_blank">first blog post</A><SPAN> </SPAN>in the series for setup instructions.</P><DIV class=""><H3 id="toc-hId--709482707">Getting Your Service Key</H3></DIV><OL><LI>Open the<SPAN> </SPAN><STRONG>SAP BTP Cockpit</STRONG></LI><LI>Navigate to your<SPAN> </SPAN><STRONG>subaccount</STRONG><SPAN> </SPAN>ā<SPAN> </SPAN><STRONG>space</STRONG><SPAN> </SPAN>ā<SPAN> </SPAN><STRONG>Service Instances</STRONG></LI><LI>Find your Job Scheduling service instance</LI><LI>Click<SPAN> </SPAN><STRONG>"Create Service Key"</STRONG><SPAN> </SPAN>(or use an existing one)</LI><LI>Open the service key and note the following values:<UL><LI><CODE>url</CODE><SPAN> </SPAN>- The base URL for the REST API - extract the Landscape Region from this (e.g.<SPAN> </SPAN><CODE>eu10</CODE><SPAN> </SPAN>from<SPAN> </SPAN><CODE><A href="https://jobscheduler-rest.cfapps.eu10.hana.ondemand.com" target="_blank" rel="noopener nofollow noreferrer">https://jobscheduler-rest.cfapps.eu10.hana.ondemand.com</A></CODE>)</LI><LI><CODE>uaa.identityzone</CODE><SPAN> </SPAN>- The zone where the Identity Authentication service is located (e.g.<SPAN> </SPAN><CODE>my-subdomain.authentication.eu10.hana.ondemand.com</CODE>)</LI><LI><CODE>uaa.clientid</CODE><SPAN> </SPAN>- Your OAuth client ID</LI><LI><CODE>uaa.clientsecret</CODE><SPAN> </SPAN>- Your OAuth client secret</LI></UL></LI></OL><P>Example service key structure:</P><DIV class=""><PRE>{
<SPAN class="">"url"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>https://jobscheduler-rest.cfapps.eu10.hana.ondemand.com<SPAN class="">"</SPAN></SPAN>,
<SPAN class="">"uaa"</SPAN>: {
<SPAN class="">"identityzone"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>my-subdomain<SPAN class="">"</SPAN></SPAN>,
<SPAN class="">"clientid"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>sb-clone-jobscheduler-service!b1234<SPAN class="">"</SPAN></SPAN>,
<SPAN class="">"clientsecret"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>your-secret-here<SPAN class="">"</SPAN></SPAN>
}
}</PRE><DIV class=""> </DIV></DIV><DIV class=""><H3 id="toc-hId--905996212">Step-by-Step: Testing GET /jobs</H3></DIV><P>Let's test the<SPAN> </SPAN><STRONG>GET /jobs</STRONG><SPAN> </SPAN>endpoint to retrieve all jobs in your Job Scheduling service instance.</P><DIV class=""><H4 id="toc-hId--1395912724">1. Configure Environment for Authentication</H4></DIV><P>Click the<SPAN> </SPAN><STRONG>"Select environment"</STRONG><SPAN> </SPAN>button:</P><DIV class=""> </DIV><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="bah-create-env.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/379675iC564627E39ADD45B/image-size/large?v=v2&px=999" role="button" title="bah-create-env.png" alt="bah-create-env.png" /></span></P><P>In the environment dialog enter:</P><OL><LI><CODE>Display name</CODE><SPAN> </SPAN>- e.g. "My Trial instance"</LI><LI><CODE>Landscape Region</CODE><SPAN> </SPAN>- e.g ap21 (trial) - extracted from the<SPAN> </SPAN><CODE>url</CODE></LI><LI><CODE>Client ID</CODE><SPAN> </SPAN>- From your service key (<CODE>uaa.clientid</CODE>)</LI><LI><CODE>Client Secret</CODE><SPAN> </SPAN>- From your service key (<CODE>uaa.clientsecret</CODE>)</LI><LI><CODE>Cf-subaccount-domain</CODE><SPAN> </SPAN>- the domain of your BTP subaccount (e.g.<SPAN> </SPAN><CODE>dadb4adetrial</CODE>) from<SPAN> </SPAN><CODE>identityzone</CODE></LI><LI><CODE>Landscape Region</CODE><SPAN> </SPAN>- again - same as above (e.g.<SPAN> </SPAN><CODE>ap21</CODE>) - yes, we need that, and yes it is confusing</LI></OL><DIV class=""> </DIV><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="bah-env.png" style="width: 624px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/379674i64A641C50DCEA800/image-size/large?v=v2&px=999" role="button" title="bah-env.png" alt="bah-env.png" /></span></P><P>If successful, you'll see a confirmation that the access token was obtained automatically.</P><DIV class=""><H4 id="toc-hId--1592426229">2. Find the GET /jobs Endpoint</H4></DIV><P>Navigate to the<SPAN> </SPAN><STRONG>Jobs</STRONG><SPAN> </SPAN>section and expand the<SPAN> </SPAN><STRONG>GET /jobs</STRONG><SPAN> </SPAN>endpoint and click<SPAN> </SPAN><STRONG>"Run"</STRONG>:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="bah-execute-get.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/379678iC17BD4905EB24FF0/image-size/large?v=v2&px=999" role="button" title="bah-execute-get.png" alt="bah-execute-get.png" /></span></P><DIV class=""><H4 id="toc-hId--1788939734">4. Configure Parameters (Optional)</H4></DIV><P>The GET /jobs endpoint supports optional query parameters:</P><UL><LI><CODE>displaySchedules</CODE><SPAN> </SPAN>(boolean) - Include schedule details in response</LI><LI><CODE>page</CODE><SPAN> </SPAN>(integer) - Page number for pagination</LI><LI><CODE>pageSize</CODE><SPAN> </SPAN>(integer) - Number of jobs per page</LI></UL><P>For this example, we'll use the defaults (no parameters needed).</P><DIV class=""><H4 id="toc-hId--1985453239">6. View the Response</H4></DIV><P>You'll see the<SPAN> </SPAN><STRONG>HTTP response</STRONG><SPAN> </SPAN>directly in the browser:</P><P><STRONG>Response Code</STRONG>:<SPAN> </SPAN><CODE>200 OK</CODE></P><P><STRONG>Response Body</STRONG><SPAN> </SPAN>(example):</P><DIV class=""><PRE>{
<SPAN class="">"total"</SPAN>: <SPAN class="">2</SPAN>,
<SPAN class="">"results"</SPAN>: [
{
<SPAN class="">"jobId"</SPAN>: <SPAN class="">2523708</SPAN>,
<SPAN class="">"name"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>validateSalesOrder<SPAN class="">"</SPAN></SPAN>,
<SPAN class="">"description"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>Validates sales order requests<SPAN class="">"</SPAN></SPAN>,
<SPAN class="">"action"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>https://my-app.cfapps.eu10.hana.ondemand.com/orders/validate<SPAN class="">"</SPAN></SPAN>,
<SPAN class="">"active"</SPAN>: <SPAN class="">true</SPAN>,
<SPAN class="">"httpMethod"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>POST<SPAN class="">"</SPAN></SPAN>,
<SPAN class="">"jobType"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>HTTP_ENDPOINT<SPAN class="">"</SPAN></SPAN>,
<SPAN class="">"ansConfig"</SPAN>: { <SPAN class="">"onError"</SPAN>: <SPAN class="">true</SPAN>, <SPAN class="">"onSuccess"</SPAN>: <SPAN class="">false</SPAN> },
<SPAN class="">"calmConfig"</SPAN>: { <SPAN class="">"enabled"</SPAN>: <SPAN class="">false</SPAN> },
<SPAN class="">"createdAt"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>2026-02-15 10:30:00<SPAN class="">"</SPAN></SPAN>
},
{
<SPAN class="">"jobId"</SPAN>: <SPAN class="">2440430</SPAN>,
<SPAN class="">"name"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>dailyReport<SPAN class="">"</SPAN></SPAN>,
<SPAN class="">"description"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>Generate daily sales report<SPAN class="">"</SPAN></SPAN>,
<SPAN class="">"action"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>https://my-app.cfapps.eu10.hana.ondemand.com/reports/daily<SPAN class="">"</SPAN></SPAN>,
<SPAN class="">"active"</SPAN>: <SPAN class="">true</SPAN>,
<SPAN class="">"httpMethod"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>GET<SPAN class="">"</SPAN></SPAN>,
<SPAN class="">"jobType"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>HTTP_ENDPOINT<SPAN class="">"</SPAN></SPAN>,
<SPAN class="">"ansConfig"</SPAN>: { <SPAN class="">"onError"</SPAN>: <SPAN class="">false</SPAN>, <SPAN class="">"onSuccess"</SPAN>: <SPAN class="">false</SPAN> },
<SPAN class="">"calmConfig"</SPAN>: { <SPAN class="">"enabled"</SPAN>: <SPAN class="">true</SPAN> },
<SPAN class="">"createdAt"</SPAN>: <SPAN class=""><SPAN class="">"</SPAN>2026-02-10 08:00:00<SPAN class="">"</SPAN></SPAN>
}
],
<SPAN class="">"prev_url"</SPAN>: <SPAN class="">null</SPAN>,
<SPAN class="">"next_url"</SPAN>: <SPAN class="">null</SPAN>
}</PRE><DIV class=""> </DIV></DIV><P><STRONG><span class="lia-unicode-emoji" title=":party_popper:">š</span> Success!</STRONG><SPAN> </SPAN>You've just called the Job Scheduling service REST API directly from your browser!</P><DIV class=""><H3 id="toc-hId--1888563737">Understanding the Response</H3></DIV><P>The response shows:</P><UL><LI><STRONG>total</STRONG><SPAN> </SPAN>- Total number of jobs in your instance</LI><LI><STRONG>results</STRONG><SPAN> </SPAN>- Array of job objects with:<UL><LI><CODE>jobId</CODE><SPAN> </SPAN>- Unique job identifier</LI><LI><CODE>name</CODE><SPAN> </SPAN>- Job name</LI><LI><CODE>description</CODE><SPAN> </SPAN>- Job description</LI><LI><CODE>action</CODE><SPAN> </SPAN>- HTTP endpoint that gets invoked</LI><LI><CODE>httpMethod</CODE><SPAN> </SPAN>- HTTP method (GET, POST, PUT, DELETE, PATCH)</LI><LI><CODE>active</CODE><SPAN> </SPAN>- Whether the job is currently active</LI><LI><CODE>jobType</CODE><SPAN> </SPAN>- Type of job (e.g.<SPAN> </SPAN><CODE>HTTP_ENDPOINT</CODE>)</LI><LI><CODE>ansConfig</CODE><SPAN> </SPAN>- Alert Notification Service settings (<CODE>onError</CODE>,<SPAN> </SPAN><CODE>onSuccess</CODE>)</LI><LI><CODE>calmConfig</CODE><SPAN> </SPAN>- SAP Cloud ALM integration settings (<CODE>enabled</CODE>)</LI><LI><CODE>createdAt</CODE><SPAN> </SPAN>- When the job was created (UTC timestamp)</LI></UL></LI><LI><STRONG>prev_url</STRONG><SPAN> </SPAN>/<SPAN> </SPAN><STRONG>next_url</STRONG><SPAN> </SPAN>- Pagination links for navigating result pages</LI></UL><DIV class=""><H3 id="toc-hId--1916893551">Tips for Exploring Other Endpoints</H3></DIV><P>Now that you know how "Try It Out" works, explore other endpoints:</P><P><STRONG>Try creating a job</STRONG>:</P><UL><LI>Use<SPAN> </SPAN><CODE>POST /jobs</CODE><SPAN> </SPAN>with a sample request body</LI><LI>See the created job immediately in the response</LI></UL><P><STRONG>Retrieve job details</STRONG>:</P><UL><LI>Copy a job ID from the<SPAN> </SPAN><CODE>GET /jobs</CODE><SPAN> </SPAN>response</LI><LI>Use<SPAN> </SPAN><CODE>GET /jobs/{jobId}</CODE><SPAN> </SPAN>to see full details including schedules</LI></UL><P><STRONG>Check run logs</STRONG>:</P><UL><LI>Find a schedule ID from a job's schedules</LI><LI>Use<SPAN> </SPAN><CODE>GET /jobs/{jobId}/schedules/{scheduleId}/runs</CODE><SPAN> </SPAN>to see execution history</LI></UL><HR /><DIV class=""><H2 id="toc-hId--1820004049"><span class="lia-unicode-emoji" title=":books:">š</span> Additional Resources</H2></DIV><DIV class=""><H3 id="toc-hId-1985046735">Official Documentation</H3></DIV><UL><LI><STRONG><A href="https://help.sap.com/docs/JOB_SCHEDULER" target="_blank" rel="noopener noreferrer">SAP Job Scheduling Service Help Portal</A></STRONG><SPAN> </SPAN>- Complete product documentation</LI><LI><STRONG><A href="https://help.sap.com/docs/job-scheduling/sap-job-scheduling-service/authentication" target="_blank" rel="noopener noreferrer">Authentication Guide</A></STRONG><SPAN> </SPAN>- OAuth 2.0 setup</LI></UL><DIV class=""><H3 id="toc-hId-1788533230">Blog Series</H3></DIV><UL><LI><STRONG><A href="https://community.sap.com/t5/technology-blogs-by-sap/job-scheduler-in-sap-business-technology-platform-overview-of-blog-posts/ba-p/13510707" target="_blank">Overview of All Blog Posts</A></STRONG><SPAN> </SPAN>- Complete tutorial series</LI></UL><DIV class=""><H3 id="toc-hId-1592019725">Discovery</H3></DIV><UL><LI><STRONG><A href="https://discovery-center.cloud.sap/serviceCatalog/job-scheduling-service" target="_blank" rel="nofollow noopener noreferrer">SAP Discovery Center</A></STRONG><SPAN> </SPAN>- Service overview and capabilities</LI></UL><HR /><DIV class=""><H2 id="toc-hId-1688909227"><span class="lia-unicode-emoji" title=":direct_hit:">šÆ</span> What's Next?</H2></DIV><P>Now that you know how to explore and test the Job Scheduling Service API on Business Accelerator Hub, you're ready for the next level:</P><P><STRONG><span class="lia-unicode-emoji" title=":open_book:">š</span> Coming Soon: Generate Your Own Job Scheduling Service Client Library</STRONG></P><P>In our next blog post, we'll show you how to:</P><UL><LI><span class="lia-unicode-emoji" title=":wrench:">š§</span><SPAN> </SPAN><STRONG>Download the OpenAPI specification</STRONG><SPAN> </SPAN>from Business Accelerator Hub</LI><LI><span class="lia-unicode-emoji" title=":gear:">āļø</span><SPAN> </SPAN><STRONG>Generate a type-safe client library</STRONG><SPAN> </SPAN>in your favorite language (Node.js, Java, Python, Go, etc.)</LI><LI><span class="lia-unicode-emoji" title=":laptop_computer:">š»</span><SPAN> </SPAN><STRONG>Use the generated client</STRONG><SPAN> </SPAN>to create jobs, schedules, and monitor execution</LI><LI><span class="lia-unicode-emoji" title=":rocket:">š</span><SPAN> </SPAN><STRONG>Build a complete working example</STRONG><SPAN> </SPAN>with authentication and error handling</LI></UL><P>Whether you're building with Node.js, Java, Python, or another language, you'll be able to generate a custom client tailored to your needs!</P><P><STRONG>Stay tuned!</STRONG><SPAN> </SPAN>Subscribe to the<SPAN> </SPAN><A href="https://community.sap.com/t5/technology-blogs-by-sap/job-scheduler-in-sap-business-technology-platform-overview-of-blog-posts/ba-p/13510707" target="_blank">SAP Job Scheduling Service blog series</A><SPAN> </SPAN>to get notified.</P><HR /><DIV class=""><H2 id="toc-hId-1492395722"><span class="lia-unicode-emoji" title=":speech_balloon:">š¬</span> Questions or Feedback?</H2></DIV><P>Have questions about the Job Scheduling Service API on Business Accelerator Hub? Found a bug or have a feature request?</P><UL><LI><STRONG>Idea</STRONG><SPAN> </SPAN>- Submit your ideas on the<SPAN> </SPAN><A href="https://influence.sap.com/sap/ino/#campaign/2277" target="_blank" rel="noopener noreferrer">Influence Portal</A></LI><LI><span class="lia-unicode-emoji" title=":books:">š</span><SPAN> </SPAN><STRONG>Help Portal</STRONG><SPAN> </SPAN>- Check the<SPAN> </SPAN><A href="https://help.sap.com/docs/JOB_SCHEDULER" target="_blank" rel="noopener noreferrer">official documentation</A><SPAN> </SPAN>and leave feedback</LI><LI><span class="lia-unicode-emoji" title=":speech_balloon:">š¬</span><SPAN> </SPAN><STRONG>Community</STRONG><SPAN> </SPAN>- Comment here in this post</LI><LI><span class="lia-unicode-emoji" title=":e_mail:">š§</span><SPAN> </SPAN><STRONG>Contact me</STRONG><SPAN> </SPAN>- Find me on Linkedin:<SPAN> </SPAN><A href="https://www.linkedin.com/in/denis-duev/" target="_blank" rel="nofollow noopener noreferrer">https://www.linkedin.com/in/denis-duev/</A></LI></UL><HR /><DIV class=""><H2 id="toc-hId-1295882217"><span class="lia-unicode-emoji" title=":party_popper:">š</span> Summary</H2></DIV><P>The SAP Job Scheduling Service REST API is now discoverable on the Business Accelerator Hub, bringing:</P><UL><LI><span class="lia-unicode-emoji" title=":white_heavy_check_mark:">ā </span><SPAN> </SPAN><STRONG>One central location</STRONG><SPAN> </SPAN>for API discovery alongside 1000+ SAP APIs</LI><LI><span class="lia-unicode-emoji" title=":white_heavy_check_mark:">ā </span><SPAN> </SPAN><STRONG>Interactive testing</STRONG><SPAN> </SPAN>with "Try It Out" - no code required</LI><LI><span class="lia-unicode-emoji" title=":white_heavy_check_mark:">ā </span><SPAN> </SPAN><STRONG>OpenAPI 3.0.3 specification</STRONG><SPAN> </SPAN>for standards-based integration</LI><LI><span class="lia-unicode-emoji" title=":white_heavy_check_mark:">ā </span><SPAN> </SPAN><STRONG>Up-to-date documentation</STRONG><SPAN> </SPAN>with examples and schemas</LI><LI><span class="lia-unicode-emoji" title=":white_heavy_check_mark:">ā </span><SPAN> </SPAN><STRONG>Foundation for client generation</STRONG><SPAN> </SPAN>(covered in our next blog!)</LI></UL><P>Whether you're just starting with Job Scheduling service or looking to integrate it into your SAP BTP applications, the Business Accelerator Hub makes it easier than ever to get started.</P><P><STRONG>Happy Scheduling!</STRONG><SPAN> </SPAN><span class="lia-unicode-emoji" title=":rocket:">š</span></P>2026-03-05T07:00:00.029000+01:00https://community.sap.com/t5/technology-blog-posts-by-sap/from-sales-data-to-design-blueprints-how-rpt-1-enabled-our-fashion-inverse/ba-p/14345282From Sales Data to Design Blueprints: How RPT-1 Enabled Our Fashion Inverse Design Engine2026-03-10T03:31:48.778000+01:00NilsDitthttps://community.sap.com/t5/user/viewprofilepage/user-id/1804917<P><STRONG><FONT size="5"><SPAN>What We Built</SPAN></FONT></STRONG></P><DIV><DIV><DIV><SPAN>The Fashion Trend Alchemist is a fully functional inverse design engine that turns historical sales data into concrete design recommendations ā complete with product visualizations, a catchy product name, and compelling sales copy. It works across product categories: coats, t-shirts, dresses, trousers, even shoes and sunglasses. The system adapts to any product type without retraining or reconfiguration.</SPAN></DIV><DIV> </DIV><DIV><DIV><DIV><FONT size="5"><STRONG>How it creates a product ā the full pipeline:</STRONG></FONT></DIV><DIV><FONT size="5"><STRONG><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="FTA_Visualization.jpeg" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/381676i947863E69D677A1A/image-size/large?v=v2&px=999" role="button" title="FTA_Visualization.jpeg" alt="FTA_Visualization.jpeg" /></span></STRONG></FONT></DIV></DIV></DIV></DIV><EM><SPAN>The product creation workflow: from context selection through AI enrichment and RPT-1 prediction to the final product profile with images, name, and sales text. The refine loop lets users iterate on any design decision.</SPAN><BR /></EM></DIV><DIV><DIV><SPAN>As shown in the diagram above, product creation flows through four core phases:</SPAN></DIV><DIV><DIV><SPAN>1.</SPAN> <SPAN><STRONG>Context Setup</STRONG></SPAN><SPAN> ā Select a product category (e.g., men's winter coats), define filters and date ranges, and let the system build a curated context of top and bottom performers from the sales data</SPAN></DIV><DIV><SPAN>2.</SPAN> <SPAN><STRONG>Enrichment</STRONG></SPAN><SPAN> ā A Vision LLM analyzes product images to extract detailed design attributes (collar type, button style, fabric weight...) that don't exist in the raw database</SPAN></DIV><DIV><SPAN>3.</SPAN> <SPAN><STRONG>Prediction</STRONG></SPAN><SPAN> ā The user specifies which attributes to lock and which to predict; RPT-1 fills in the optimal values using in-context learning from the enriched dataset</SPAN></DIV><DIV><SPAN>4.</SPAN> <SPAN><STRONG>Product Profile</STRONG></SPAN><SPAN> ā The system generates multi-view product images, proposes a product name, and drafts sales copy ā creating a complete, tangible design concept that can be refined and saved to a collection</SPAN></DIV><DIV> </DIV><DIV><DIV><STRONG><FONT size="5">Technical Architecture</FONT></STRONG></DIV><DIV><DIV><SPAN>This isn't a Jupyter notebook experiment ā it's a deployed, interactive application where users explore design decisions in real time.</SPAN></DIV><DIV> <span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="FTA_TechnicalArchitecture.drawio.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/381677i50448B14A9FAA007/image-size/large?v=v2&px=999" role="button" title="FTA_TechnicalArchitecture.drawio.png" alt="FTA_TechnicalArchitecture.drawio.png" /></span></DIV><DIV><DIV><EM>The Fashion Trend Alchemist runs on SAP BTP's Kyma runtime as a three-container deployment. The React frontend communicates with a Fastify/Node.js backend that orchestrates three AI services: RPT-1 for attribute prediction and GPT-4.1 for data enrichment and text generation (both via SAP AI Core), and a **self-hosted Z-Image Turbo** instance (product visualization, deployed as a Kyma microservice). PostgreSQL, Redis, and SeaweedFS handle data persistence, caching, and image storage respectively.</EM></DIV><DIV> </DIV><DIV><DIV><FONT size="5"><STRONG>How It Works: Designing a Men's Winter Coat</STRONG></FONT></DIV><DIV><DIV><SPAN>Let's walk through a concrete example to see each phase in action: a product manager wants to know what the next successful men's winter coat should look like.</SPAN></DIV><DIV> </DIV><DIV><DIV><STRONG>Building the Context</STRONG></DIV><DIV><DIV>The user selects "Coats" as the product type, filters for winter seasons and men's wear, and the system queries a large transaction dataset for matching products.</DIV><DIV><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ContextBuilding.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/381680i70555597C4F561BF/image-size/large?v=v2&px=999" role="button" title="ContextBuilding.png" alt="ContextBuilding.png" /></span></DIV><DIV><DIV><SPAN>For each matching product, the system calculates a </SPAN><SPAN><STRONG>velocity score</STRONG></SPAN><SPAN> ā first computing units sold per day of availability (transaction count divided by days between first and last sale), then converting this to a percentile rank (0-100) across all matching products. A coat that sold 200 units in 30 days scores higher than one that sold 200 units over 6 months. The system then selects the top and bottom performers to create a balanced context showing RPT-1 clear examples of "success" and "failure."</SPAN></DIV><DIV> </DIV><DIV><DIV><STRONG>Enriching Products with Design Attributes</STRONG></DIV><DIV><DIV><SPAN>Fashion databases store generic columns: color, material, product group. But a coat's success depends on collar type, insulation level, and closure style ā attributes that don't exist in the raw data.</SPAN></DIV><BR /><DIV><SPAN>We solve this with AI enrichment. First, an LLM generates a product-specific attribute schema (e.g., </SPAN><FONT color="#FF6600"><EM>`<FONT color="#FF6600">collar_type`</FONT></EM><EM>, `button_style`, `insulation_level`</EM></FONT><SPAN> with their possible values). Then, GPT-4.1 analyzes each product's image and extracts these attributes as structured data.</SPAN></DIV><DIV><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Enrichment.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/381681i753526E38AD4DA0C/image-size/large?v=v2&px=999" role="button" title="Enrichment.png" alt="Enrichment.png" /></span></SPAN></DIV><DIV><DIV><DIV><EM>Real-time enrichment with progress tracking ā GPT-4.1 extracts structured design attributes from product images.</EM></DIV><DIV> </DIV><DIV><DIV><DIV><STRONG>RPT-1 in Action: The Prediction</STRONG></DIV><DIV><DIV><DIV><SPAN>Now the core moment. The enriched context ā ~50 products with their detailed attributes and velocity scores ā is sent to RPT-1. The user constructs a </SPAN><SPAN><STRONG>query row</STRONG></SPAN><SPAN> where some attributes are fixed and others contain </SPAN><FONT color="#FF6600"><EM>`[PREDICT]`</EM></FONT><SPAN> placeholders.</SPAN></DIV><DIV> </DIV><DIV><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="RPT-1Prediction.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/381682i24299306986C01B9/image-size/large?v=v2&px=999" role="button" title="RPT-1Prediction.png" alt="RPT-1Prediction.png" /></span></SPAN></DIV></DIV></DIV></DIV></DIV></DIV></DIV><DIV> <DIV><DIV><SPAN>The interface uses a three-column layout:</SPAN></DIV><BR /><DIV><SPAN>-</SPAN> <SPAN><STRONG>Locked</STRONG></SPAN><SPAN> (left): Attributes the user wants to fix ā "I know I want a Navy coat"</SPAN></DIV><DIV><SPAN>-</SPAN> <SPAN><STRONG>AI-Predicted</STRONG></SPAN><SPAN> (center): Attributes RPT-1 should determine ā "What collar type? What button style?"</SPAN></DIV><DIV><SPAN>-</SPAN> <SPAN><STRONG>Excluded</STRONG></SPAN><SPAN> (right): Attributes irrelevant to this prediction</SPAN></DIV><DIV> </DIV><DIV><DIV><SPAN>The user sets a target success score and clicks "Transmute." RPT-1 receives the full context plus the query row in a single API call ā no model training, no feature engineering, no pipeline orchestration. Formatted tabular data in, predictions out.</SPAN></DIV><BR /><DIV><SPAN>The entire ML layer is essentially: </SPAN><SPAN>_format data as rows ā add query row with </SPAN><FONT color="#FF6600"><EM>`[PREDICT]`</EM></FONT><SPAN> ā call API ā parse response_</SPAN><SPAN>. The system complexity lives in data preparation, not model management.</SPAN></DIV><DIV> </DIV><DIV><DIV><STRONG>Bringing It to Life: The Full Product Profile</STRONG></DIV><DIV><DIV><SPAN>The predicted attributes are combined with locked values to generate a complete product profile. The system translates the attribute combination into structured image prompts and produces multiple product views via a self-hosted image generation model running on Kyma ā using category-aware prompt templates (coats use ghost mannequin photography; footwear uses product pair shots; accessories get contextual framing). This is completed by a catchy product name and a sales text.</SPAN></DIV><DIV> </DIV><DIV><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ImageGeneration.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/381683i7664A70DC86C050D/image-size/large?v=v2&px=999" role="button" title="ImageGeneration.png" alt="ImageGeneration.png" /></span></SPAN></DIV><DIV><DIV><SPAN>The result isn't an abstract list of attributes ā it's a concrete product concept that a designer or product manager can evaluate, iterate on, and refine. The refine loop shown in the pipeline diagram lets users adjust any attribute and regenerate until they're satisfied, then save the design to a collection.</SPAN></DIV><DIV> </DIV><DIV><DIV><FONT size="5"><STRONG>What We Learned About RPT-1</STRONG></FONT></DIV><DIV><DIV><STRONG>Prediction Becomes a Data Formatting Problem</STRONG></DIV><DIV><DIV><SPAN>RPT-1 is pretrained and uses in-context learning ā you don't train the model, you provide examples and a query. The API structure is remarkably simple:</SPAN></DIV><DIV><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="RPT-1Query.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/381684i600915C511956D2C/image-size/large?v=v2&px=999" role="button" title="RPT-1Query.png" alt="RPT-1Query.png" /></span></SPAN></DIV><DIV><DIV><SPAN>The entire prediction integration is ~50 lines of TypeScript. No training scripts, no model serialization, no serving infrastructure. This drastically lowers the barrier for teams outside ML/data science ā if you can format tabular data, you can use RPT-1.</SPAN></DIV><BR /><DIV><SPAN>Dynamic schemas come free: coats need </SPAN><SPAN>`collar_type`</SPAN><SPAN> and </SPAN><SPAN>`button_style`</SPAN><SPAN>, sunglasses need </SPAN><SPAN>`lens_tint`</SPAN><SPAN> and </SPAN><SPAN>`frame_shape`</SPAN><SPAN>. With traditional ML, changing features means re-encoding, retraining, revalidating. With RPT-1, just change the columns.</SPAN></DIV><DIV> </DIV><DIV><DIV><STRONG>Validating RPT-1 in the Fashion Domain</STRONG></DIV><DIV><DIV><SPAN>Our goal wasn't to build a production prediction system ā it was to test whether RPT-1's in-context learning could handle fashion data meaningfully. To validate this, we inverted the workflow: instead of predicting attributes from success scores (inverse design), we tested how well RPT-1 predicts success from attributes (forward prediction). This serves as a sanity check ā if the model understands attribute-success relationships, it can leverage them for inverse predictions.</SPAN></DIV><BR /><DIV><SPAN>We benchmarked RPT-1 against traditional gradient boosting models (XGBoost, CatBoost) across five product categories using an 80/20 random split:</SPAN></DIV><DIV><TABLE border="1" width="100%"><TBODY><TR><TD width="25%" height="30px"><DIV><DIV><STRONG>Model</STRONG></DIV></DIV></TD><TD width="25%" height="30px"><DIV><DIV><STRONG>R² </STRONG></DIV></DIV></TD><TD width="25%" height="30px"><DIV><DIV><STRONG>NDCG </STRONG></DIV></DIV></TD><TD width="25%" height="30px"><DIV><DIV><STRONG>Hit@25</STRONG></DIV></DIV></TD></TR><TR><TD width="25%" height="30px"><DIV><DIV><SPAN>RPT-1</SPAN></DIV></DIV></TD><TD width="25%" height="30px"><DIV><DIV><SPAN>+0.302 </SPAN></DIV></DIV></TD><TD width="25%" height="30px"><DIV><DIV><STRONG>0.954</STRONG></DIV></DIV></TD><TD width="25%" height="30px"><DIV><DIV><STRONG>60.5%</STRONG></DIV></DIV></TD></TR><TR><TD width="25%" height="30px"><DIV><DIV><SPAN>XGBoost</SPAN></DIV></DIV></TD><TD width="25%" height="30px"><DIV><DIV><SPAN>+0.267 </SPAN></DIV></DIV></TD><TD width="25%" height="30px"><DIV><DIV><SPAN>0.946</SPAN></DIV></DIV></TD><TD width="25%" height="30px">55.1%</TD></TR><TR><TD width="25%" height="30px"><DIV><DIV><SPAN>CatBoost</SPAN></DIV></DIV></TD><TD width="25%" height="30px"><DIV><DIV><SPAN>+0.258 </SPAN></DIV></DIV></TD><TD width="25%" height="30px"><DIV><DIV><SPAN>0.945</SPAN></DIV></DIV></TD><TD width="25%" height="30px"> 56.4%</TD></TR></TBODY></TABLE><DIV><DIV><EM>Aggregated across five product categories. RPT-1 matches established gradient boosting models on absolute fit (R²) and edges them out on ranking quality (NDCG, Hit@25) ā the metrics that matter most for separating winners from losers.</EM></DIV><DIV> </DIV><DIV><DIV><SPAN>The scatter plot below shows this concretely for ladies' dresses: top performers (Q4, green) cluster where predicted, while bottom performers (Q1, red) separate clearly. The model isn't perfect, but the quartile separation confirms RPT-1 successfully learned from the in-context examples.</SPAN><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="row_order_dress_ladies_all_seasons_rank_database_order.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/381685i73ABB1EC108D5022/image-size/large?v=v2&px=999" role="button" title="row_order_dress_ladies_all_seasons_rank_database_order.png" alt="row_order_dress_ladies_all_seasons_rank_database_order.png" /></span></DIV><DIV><DIV><EM>Actual vs. predicted rank for Ladies All-Season Dresses (Ļ = 0.626). Clear quartile separation demonstrates RPT-1 effectively distinguishes high from low performers using only in-context examples.</EM></DIV><DIV> </DIV><DIV><DIV><SPAN><STRONG>The key finding:</STRONG></SPAN><SPAN> RPT-1 worked out-of-the-box for fashion data without domain-specific tuning. For teams exploring tabular foundation models in new domains, this suggests the pretrained capabilities transfer broadly. RPT-1's in-context learning architecture particularly shines in low-data scenarios ā some niche categories had only ~23 items in the context, where RPT-1 handled these gracefully.</SPAN></DIV><DIV> </DIV><DIV> </DIV><DIV><DIV> </DIV><DIV> </DIV><DIV><STRONG>Context Quality Matters More Than Context Structure</STRONG></DIV><DIV><DIV><SPAN>We systematically tested what affects RPT-1's prediction quality:</SPAN></DIV><DIV> </DIV><DIV><SPAN><STRONG>Row and column ordering</STRONG> ā by design, RPT-1's architecture is invariant to the ordering of rows and columns, so reordering should not affect predictions (up to minor numerical artifacts). Our experiments confirmed this: shuffling context rows or reordering columns produced no measurable difference in results ā exactly as expected.<BR /></SPAN></DIV><BR /><DIV><STRONG>Column naming</STRONG> ā inherently relevant, though RPT-1 proved surprisingly resilient. Even when we degraded names to synonyms, German translations, or numeric codes, performance variations were minor and inconsistent. Descriptive column names remain a best practice, but the model handles imperfect naming gracefully.<BR /><BR /></DIV><DIV> </DIV><DIV><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="naming_chaos.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/381686i7C018781DD3D1BE2/image-size/large?v=v2&px=999" role="button" title="naming_chaos.png" alt="naming_chaos.png" /></span></SPAN></DIV><DIV><DIV><DIV>Spearman correlation across naming conditions (Original, Synonyms, German, Mixed, Legacy, Chaos, Numeric). RPT-1 proved robust across conditions in our dataset, though meaningful column names remain a best practice for optimal predictions.</DIV><DIV> </DIV></DIV><DIV><SPAN><STRONG>Out-of-distribution contamination</STRONG></SPAN><SPAN> ā high impact. When wrong products polluted the context (e.g., shirts mixed into a hoodies dataset), prediction quality degraded significantly.</SPAN></DIV><DIV> </DIV><DIV><SPAN><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="ood_contamination.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/381687iDC680E28EAC51BD7/image-size/large?v=v2&px=999" role="button" title="ood_contamination.png" alt="ood_contamination.png" /></span></SPAN></DIV><DIV><DIV><DIV><DIV><EM>Spearman correlation drops as OOD contamination increases from 0% to 40%. Clean data matters far more than clever formatting.</EM></DIV></DIV><SPAN><BR /><STRONG>The takeaway: RPT-1 handles structural variations gracefully ā row/column ordering has no effect, and naming degradation had limited impact in our tests. Your primary effort should go into data cleaning and proper normalization.</STRONG><BR /><BR /></SPAN></DIV><DIV><DIV><SPAN><FONT size="5"><STRONG>Challenges We Faced and How We Solved Them</STRONG></FONT></SPAN></DIV><DIV><STRONG>Real-World Data is Messy</STRONG></DIV><BR /><DIV><SPAN><STRONG>Problem:</STRONG></SPAN><SPAN> Product catalogs contain labeling errors ā shirts categorized as hoodies, skirts labeled as pants. In-context learning amplifies these errors: garbage in, garbage out.</SPAN></DIV><BR /><DIV><SPAN><STRONG>Solution:</STRONG></SPAN><SPAN> Our Vision LLM enrichment returns a </SPAN><EM><FONT color="#FF6600">`mismatchConfidence`</FONT></EM><SPAN> score (0-100) alongside extracted attributes. Products scoring above 80 are flagged for human review. The user sees a prefiltered list and can correct or exclude problematic items before prediction. What started as an afterthought became essential.</SPAN></DIV><BR /><DIV><FONT size="5"><STRONG>GenAI Output is Unpredictable</STRONG></FONT></DIV><BR /><DIV><SPAN><STRONG>Problem:</STRONG></SPAN><SPAN> LLM outputs can be wrong or malformed ā invalid JSON, attributes outside valid ranges, unexpected structures.</SPAN></DIV><BR /><DIV><SPAN><STRONG>Solution:</STRONG></SPAN><SPAN> Multi-layer guardrails:</SPAN></DIV><BR /><DIV><SPAN>-</SPAN><SPAN> Prompting with positive examples and explicit output structure</SPAN></DIV><DIV><SPAN>-</SPAN><SPAN> Runtime validation with </SPAN><SPAN><STRONG>Zod schemas</STRONG></SPAN><SPAN> to catch structural errors</SPAN></DIV><DIV><SPAN>-</SPAN><SPAN> Attribute values validated against the ontology's allowed options</SPAN></DIV><DIV><SPAN>-</SPAN><SPAN> Retry logic with exponential backoff (up to 3 attempts)</SPAN></DIV><DIV> </DIV><DIV><DIV><SPAN>Users can manually adjust results or send them back to the LLM with feedback.</SPAN></DIV><DIV><FONT size="4"><STRONG> </STRONG></FONT></DIV><DIV><DIV><FONT size="4"><STRONG>Consistency Across Generated Images</STRONG></FONT></DIV><DIV><DIV><SPAN><STRONG>Problem:</STRONG></SPAN><SPAN> Three product views (front, back, model) must show the same design. Different prompts lead to inconsistent products.</SPAN></DIV><BR /><DIV><SPAN><STRONG>Solution:</STRONG></SPAN><SPAN> The LLM generates a single shared </SPAN><EM><FONT color="#FF6600">`productDescription`</FONT></EM><SPAN> used as the consistent core across all views, combined with view-specific prefixes and category-aware templates (wearables use ghost mannequin photography; footwear uses product pair shots).</SPAN></DIV><DIV> </DIV><DIV><DIV><FONT size="5"><STRONG>Closing</STRONG></FONT></DIV><BR /><DIV><SPAN><STRONG>Three takeaways for your next project:</STRONG></SPAN></DIV><BR /><DIV><SPAN>1.</SPAN> <SPAN><STRONG>RPT-1 turns prediction into a data selection & formatting problem.</STRONG></SPAN><SPAN> No training pipeline, no ML expertise required ā just clean tabular data with a success metric.</SPAN></DIV><BR /><DIV>2.<STRONG> Context quality is paramount.</STRONG> Row and column ordering has no effect on RPT-1 by design, and the model proved resilient to naming variations in our experiments ā though descriptive column names remain recommended. What matters most is clean, relevant data: wrong items in the context degrade predictions significantly.</DIV><BR /><DIV><SPAN>3.</SPAN> <SPAN><STRONG>Build guardrails around GenAI.</STRONG> </SPAN><SPAN>Validate LLM outputs with schemas, constrain responses to allowed values, and keep humans in the loop for edge cases.</SPAN></DIV><DIV> </DIV><DIV> </DIV><DIV><DIV><DIV><EM>Developed at BTP Solution Advisory APAC by Danylo Polishchuk and Nils Dittrich.</EM></DIV><DIV> </DIV><DIV><DIV><DIV><SPAN>For questions or collaboration opportunities, connect with us on LinkedIn: <A href="https://www.linkedin.com/in/danylo-polishchuk-b365473b0/" target="_blank" rel="noopener nofollow noreferrer">Danylo Polishchuk</A> | <A href="https://www.linkedin.com/in/nils-dittrich-667991308" target="_self" rel="nofollow noopener noreferrer">Nils Dittrich.</A></SPAN></DIV><DIV> </DIV><DIV><DIV><DIV><STRONG>Want to see the Fashion Trend Alchemist in action?</STRONG><SPAN> Check out the </SPAN><A href="https://sapvideo.cfapps.eu10-004.hana.ondemand.com/?entry_id=1_9tpnzdcg" target="_self" rel="nofollow noopener noreferrer"><SPAN>demo video</SPAN></A><SPAN> for a walkthrough of the complete workflow.</SPAN></DIV><BR /><DIV><STRONG>Want to try RPT-1 yourself?</STRONG><SPAN> Explore the </SPAN><A href="https://rpt.cloud.sap/login?next=%2Fdashboard" target="_self" rel="nofollow noopener noreferrer"><SPAN>RPT-1 Playground </SPAN></A><SPAN>to test tabular predictions with your own data.</SPAN></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV></DIV>2026-03-10T03:31:48.778000+01:00https://community.sap.com/t5/technology-blog-posts-by-members/getting-started-with-sap-btp-kyma-runtime-using-the-cli/ba-p/14347069Getting Started with SAP BTP Kyma Runtime Using the CLI2026-03-12T10:27:43.536000+01:00neilaspinhttps://community.sap.com/t5/user/viewprofilepage/user-id/167493<P>Introduction</P><P>If you're learning SAP BTP Kyma Runtime, most tutorials point you straight to the cockpit UI. But understanding the CLI tools ā <CODE>btp</CODE> and <CODE>kubectl</CODE> ā gives you a much deeper understanding of what's actually happening under the hood. In this post I'll walk through connecting to a Kyma cluster and exploring its core components purely from the command line.</P><HR /><H2 id="toc-hId-1791516416">Prerequisites</H2><UL><LI>SAP BTP Trial account with Kyma Runtime enabled</LI><LI><CODE>btp</CODE><SPAN> </SPAN>CLI installed (<A href="https://tools.hana.ondemand.com/#cloud" target="_blank" rel="noopener noreferrer nofollow">download here</A>)</LI><LI><CODE>kubectl</CODE><SPAN> </SPAN>installed</LI></UL><HR /><H2 id="toc-hId-1595002911">Step 1: Find Your Subaccount and Kyma Instance</H2><P>Log in to BTP via CLI and find your subaccount:</P><DIV class=""><PRE><CODE>btp login
btp list accounts/subaccount</CODE></PRE></DIV><P>Then find your Kyma environment instance:</P><DIV class=""><PRE><CODE>btp list accounts/environment-instance --subaccount <subaccount-id></CODE></PRE></DIV><P>You should see something like:</P><DIV class=""><PRE><CODE>environment name environment id environment type state
<subdomain>-7tn04i0d <environment-id> kyma OK</CODE></PRE></DIV><P>Get the details including your kubeconfig URL:</P><DIV class=""><PRE><CODE>btp get accounts/environment-instance <environment-id> --subaccount <subaccount-id></CODE></PRE></DIV><P>This returns a <CODE>labels</CODE> field containing your <CODE>KubeconfigURL</CODE> and <CODE>APIServerURL</CODE>.</P><HR /><H2 id="toc-hId-1398489406">Step 2: Download the Kubeconfig and Connect kubectl</H2><P>Download the kubeconfig from the Kyma Dashboard or via the KubeconfigURL, then point kubectl at it:</P><DIV class=""><PRE><CODE>cp ~/Downloads/kubeconfig.yaml ~/.kube/kyma-config.yaml
export KUBECONFIG=~/.kube/kyma-config.yaml
kubectl get nodes</CODE></PRE></DIV><P>On a trial cluster you'll see an AWS EC2 worker node:</P><DIV class=""><PRE><CODE>NAME STATUS ROLES AGE VERSION
<node-name>.ec2.internal Ready worker 7d v1.33.7</CODE></PRE></DIV><HR /><H2 id="toc-hId-1201975901">Step 3: Explore the Kyma Cluster</H2><H3 id="toc-hId-1134545115">Namespaces</H3><DIV class=""><PRE><CODE>kubectl get namespaces</CODE></PRE></DIV><P>Key namespaces:</P><P>Namespace Purpose</P><TABLE><TBODY><TR><TD><CODE>kyma-system</CODE></TD><TD>Core Kyma components</TD></TR><TR><TD><CODE>istio-system</CODE></TD><TD>Service mesh</TD></TR><TR><TD><CODE>default</CODE></TD><TD>Your workloads</TD></TR></TBODY></TABLE><H3 id="toc-hId-938031610">Core Components in kyma-system</H3><DIV class=""><PRE><CODE>kubectl get pods -n kyma-system</CODE></PRE></DIV><P>Pod Purpose</P><TABLE><TBODY><TR><TD><CODE>api-gateway-controller-manager</CODE></TD><TD>Manages APIRule resources</TD></TR><TR><TD><CODE>btp-manager-controller-manager</CODE></TD><TD>Manages BTP service bindings</TD></TR><TR><TD><CODE>istio-controller-manager</CODE></TD><TD>Manages Istio service mesh</TD></TR><TR><TD><CODE>sap-btp-operator-controller-manager</CODE></TD><TD>Provisions BTP services as Kubernetes resources</TD></TR><TR><TD><CODE>warden-*</CODE></TD><TD>Image trust validation</TD></TR></TBODY></TABLE><H3 id="toc-hId-741518105">Kyma Custom Resources (CRDs)</H3><DIV class=""><PRE><CODE>kubectl api-resources | grep kyma</CODE></PRE></DIV><P>Key CRDs:</P><P>Resource Purpose</P><TABLE><TBODY><TR><TD><CODE>APIRule</CODE></TD><TD>Expose services externally with auth/routing</TD></TR><TR><TD><CODE>RateLimit</CODE></TD><TD>Throttle API traffic</TD></TR><TR><TD><CODE>BtpOperator</CODE></TD><TD>Configure BTP service operator</TD></TR><TR><TD><CODE>Kyma</CODE></TD><TD>Top-level Kyma installation resource</TD></TR></TBODY></TABLE><HR /><H2 id="toc-hId-415921881">Step 4: Understanding an APIRule</H2><P>The <CODE>APIRule</CODE> is the most important Kyma concept for exposing workloads. Here's what a typical one looks like:</P><DIV class=""><PRE><CODE>apiVersion: gateway.kyma-project.io/v2
kind: APIRule
spec:
gateway: kyma-system/kyma-gateway
hosts:
- myapp.<cluster-id>.kyma.ondemand.com
rules:
- methods: [GET, POST]
noAuth: true
path: /*
service:
name: myapp
port: 80</CODE></PRE></DIV><H3 id="toc-hId-348491095">Traffic flow:</H3><DIV class=""><PRE><CODE>Internet
ā https://myapp.<cluster-id>.kyma.ondemand.com
ā kyma-gateway (Istio ingress in kyma-system)
ā Service: myapp:80
ā Pod (with Istio sidecar injected automatically)</CODE></PRE></DIV><P>Note the <CODE>noAuth: true</CODE> setting ā fine for development but in production you'd replace this with a JWT handler pointing to XSUAA or another identity provider.</P><HR /><H2 id="toc-hId-22894871">Key Observations</H2><UL><LI><STRONG>Istio sidecar injection</STRONG><SPAN> </SPAN>is automatic ā any pod in a labelled namespace gets a second container (<CODE>2/2</CODE><SPAN> </SPAN>in<SPAN> </SPAN><CODE>kubectl get pods</CODE>)</LI><LI><STRONG>APIRules replace Ingress</STRONG><SPAN> </SPAN>in Kyma ā don't use standard Kubernetes Ingress resources</LI><LI><STRONG>BTP services bind as Kubernetes Secrets</STRONG><SPAN> </SPAN>ā via<SPAN> </SPAN><CODE>ServiceInstance</CODE><SPAN> </SPAN>and<SPAN> </SPAN><CODE>ServiceBinding</CODE><SPAN> </SPAN>resources managed by the SAP BTP Operator</LI></UL><HR /><H2 id="toc-hId-173635723">Next Steps</H2><UL><LI>Bind a BTP service (XSUAA, Destination Service) using<SPAN> </SPAN><CODE>ServiceInstance</CODE><SPAN> </SPAN>and<SPAN> </SPAN><CODE>ServiceBinding</CODE></LI><LI>Secure an APIRule with JWT authentication</LI><LI>Deploy a Kyma Serverless Function</LI></UL>2026-03-12T10:27:43.536000+01:00https://community.sap.com/t5/artificial-intelligence-blogs-posts/how-our-team-started-using-ai-to-supercharge-daily-dev-work-in-sap/ba-p/14347072How Our Team Started Using AI to Supercharge Daily Dev Work in SAP Logistics Management2026-03-12T10:28:51.321000+01:00eric_wang_07https://community.sap.com/t5/user/viewprofilepage/user-id/252626<P><STRONG>Taming the Dependency Update Avalanche </STRONG></P><P>If you've ever managed dependency updates across multiple repositories, you know the pain. Every morning, there's a fresh batch of version bumps waiting for you. Click into GitHub, check the build status, review the diff, approve, merge, repeat. Multiply that by half a dozen repos, and congratulations ā your morning is gone. With AI assistance, we condensed that entire ritual into a single conversation. Ask the AI to check all open dependency PRs, get a summary table with build status and update needs, then tell it which ones to approve. What used to be a 20-minute clicking marathon now takes seconds. It's not glamorous, but reclaiming that time every single day adds up fast.</P><P><STRONG>From Task Description to Implementation Plan ā Without the Ramp-Up </STRONG></P><P>We've all been there: you pick up a task from the backlog, and you have zero context. The description references some integration pipeline, a data model you've never touched, and a field mapping buried in documentation you didn't know existed. Instead of spending an hour ramping up, we started asking the AI to do the legwork. It reads the task description, pulls up relevant documentation, cross-references technical specs, and even verifies assumptions against design documents. Then it drafts an implementation plan ā sometimes spinning up parallel research threads to speed things up. The key insight here isn't that AI writes perfect plans. It's that the research phase ā the part where you're just gathering context ā gets compressed dramatically. You still make the decisions, but you make them faster because the information is already in front of you.</P><P><STRONG>Rolling Out Fixes Across Multiple Repos </STRONG></P><P>Here's a scenario every team knows: you find a bug that affects multiple repositories. The fix is straightforward, but applying it means cloning each repo, making the same change, creating a PR, writing the description ā rinse and repeat. We found that AI assistants are surprisingly good at this. Describe the issue once, point it at the affected repos, and let it implement the fix across all of them. It even creates individual pull requests with proper descriptions. Three PRs from a single conversation, no copy-pasting required. This is the kind of task where AI doesn't need to be creative ā it just needs to be consistent and thorough. And it nails that.</P><P><STRONG>Build Failure Detective Work </STRONG></P><P>The classic "hey, can you check why the pipeline failed?" message. Sometimes you're in a meeting, sometimes you're just deep in another task and don't want to context-switch. Build logs are long, noisy, and full of red herrings. We started delegating the initial investigation to AI. It digs through CI/CD build logs, identifies the actual failures buried in the noise, and gives you a verdict: is this a safe update that just needs a test fix, or is there a genuine regression? The kind of log archaeology that normally takes 15 minutes of scrolling and squinting ā done in about one. You still make the call on what to do next. But at least you're making it with a clear summary instead of raw log output.</P><P><STRONG>What We Actually Learned</STRONG></P><P>After a few weeks of working this way, a few things became clear:</P><UL><LI><STRONG>AI is best at the boring stuff</STRONG>. The more repetitive and well-defined a task is, the more value you get. Don't start with "write me a new microservice." Start with "check these 12 PRs for me."</LI><LI><STRONG>Trust, but verify.</STRONG> AI will confidently give you wrong answers sometimes. Cross-checking matters. The good news is that the AI itself can help you verify ā ask it to double-check against documentation or specs.</LI><LI><STRONG>Context is everything.</STRONG> The more your AI assistant understands your codebase, conventions, and tooling, the more useful it becomes. Invest time in setting that up ā it pays off quickly.</LI><LI><STRONG>It changes how you plan your day.</STRONG> When you know you can offload the tedious parts, you start structuring your work differently. Kick off an AI task before a meeting, review the results after. It's like having a junior developer who never gets bored.</LI></UL>2026-03-12T10:28:51.321000+01:00https://community.sap.com/t5/technology-blog-posts-by-sap/update-btp-trial-update-kyma-runtime-availability/ba-p/14362992[UPDATE] BTP Trial Update: Kyma Runtime Availability2026-03-31T17:18:32.448000+02:00marcoporruhttps://community.sap.com/t5/user/viewprofilepage/user-id/592649<P>Dear SAP BTP trial community,</P><P>We always aimed to give users the most freedom and flexibility in the SAP BTP trial to ensure that everyone can test and explore our software or use it for learning purposes. For the next weeks, the SAP BTP trial will not be able to deliver this experience for new SAP BTP trial accounts as we have to prioritize the security and reliability of our SAP BTP trial system. We are experiencing an unusually high amount of misuse limiting the availability for all of our users. </P><P>Therefore, we decided to temporarily reduce the offering for the SAP BTP Kyma runtime for new BTP Trial Accounts. You will still be able to build applications and preview them, but deployment in the SAP BTP Kyma runtime will not be possible for the next weeks. </P><P>How can you still explore SAP BTP? </P><UL><LI>All other services will remain available and can be explored on the <A href="https://discovery-center.cloud.sap/viewServices?regions=all&commercialModel=trial" target="_blank" rel="noopener nofollow noreferrer">SAP Discovery Center</A></LI><LI>As a partner: Refer to our newly released <A href="https://news.sap.com/2025/08/partners-free-sap-build-licenses-tdd-ai-powered-intelligent-applications/" target="_blank" rel="noopener noreferrer">free TDD licenses</A></LI><LI>As a customer: Leverage the free tier offering as part of Pay-As-You-Go for SAP BTP available in the <A href="https://www.sap.com/store.html" target="_blank" rel="noopener noreferrer">SAP Store</A></LI></UL><P>All updates will be posted in the SAP Community. </P><P> </P><P>See you soon, </P><P>SAP BTP trial product team</P>2026-03-31T17:18:32.448000+02:00https://community.sap.com/t5/technology-blog-posts-by-members/calling-the-sap-business-partner-api-from-a-kyma-serverless-function-via/ba-p/14285338Calling the SAP Business Partner API from a Kyma Serverless Function via BTP Destination Service2026-04-02T10:12:48.110000+02:00neilaspinhttps://community.sap.com/t5/user/viewprofilepage/user-id/167493<H2 id="toc-hId-1766524556">Introduction</H2><P>My <A href="https://community.sap.com/t5/technology-blog-posts-by-members/calling-the-sap-business-partner-api-from-a-kyma-serverless-function-via/ba-p/14285338" target="_blank">previous post</A> walked through calling the SAP Business Partner API from a Kyma Serverless Function. That post was written against a BTP trial account where the Kyma environment came fully loaded ā Serverless enabled, full cockpit access, no restrictions.</P><P>When I came to apply the same approach on a free tier account, I hit two blockers straight away. These are the same constraints you'd likely encounter trying to apply this pattern on a real engagement, where you're working on a shared cluster and don't own the BTP subaccount:</P><OL><LI><STRONG>The Serverless module wasn't installed</STRONG> ā <CODE>kind: Function</CODE> doesn't exist as a resource type, so the function YAML simply won't apply</LI><LI><STRONG>No permission to create BTP Destinations via the cockpit</STRONG> ā the New Destination button was greyed out</LI></OL><P>This post is about working around both. By the end you'll have the same result as the original ā a live endpoint returning SAP Business Partner JSON ā but deployed as a standard Kubernetes Deployment instead of a Kyma Function, and with the BTP Destination created via the REST API rather than the cockpit.</P><HR /><H2 id="toc-hId-1570011051">What Changed vs the Original Approach</H2><P>Original This Post</P><TABLE><TBODY><TR><TD><CODE>kind: Function</CODE> (Kyma Serverless)</TD><TD><CODE>kind: Deployment</CODE> (standard Kubernetes)</TD></TR><TR><TD>Code inline in YAML</TD><TD>Code in a Docker container</TD></TR><TR><TD>No registry needed</TD><TD>Image pushed to GitHub Container Registry</TD></TR><TR><TD>Destination created in BTP cockpit</TD><TD>Destination created via Destination Service REST API</TD></TR></TBODY></TABLE><P>The call chain is identical ā your container still calls XSUAA for a token, resolves the destination, then calls the SAP API sandbox. The difference is purely in how the code is packaged and deployed.</P><HR /><H2 id="toc-hId-1373497546">Prerequisites</H2><UL><LI><CODE>kubectl</CODE> configured against a Kyma cluster</LI><LI>Docker installed locally</LI><LI>A GitHub account (for the container registry)</LI><LI>An account on <A href="https://api.sap.com/" target="_blank" rel="noopener noreferrer">api.sap.com</A> with an API key</LI></UL><HR /><H2 id="toc-hId-1176984041">Step 1: Check What's Actually Available</H2><P>Before spending time on a Kyma Function approach, verify whether Serverless is installed:</P><PRE><CODE>kubectl api-resources | grep serverless</CODE></PRE><P>If nothing is returned, the Serverless module isn't enabled. You'll need either cluster admin access to enable it, or the approach in this post.</P><P>Also verify the BTP Service Operator is available (needed for ServiceInstance and ServiceBinding):</P><PRE><CODE>kubectl api-resources | grep services.cloud.sap.com</CODE></PRE><P>You should see <CODE>serviceinstances</CODE> and <CODE>servicebindings</CODE>. If these are missing, stop here ā the BTP Service Operator isn't installed and none of this will work.</P><HR /><H2 id="toc-hId-980470536">Step 2: Create the Destination Service Instance and Binding</H2><P>This is identical to the original post. Apply these to your namespace:</P><P><STRONG>k8s/destination-instance.yaml</STRONG></P><PRE><CODE>apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: destination-service
namespace: dev-space-neil
spec:
serviceOfferingName: destination
servicePlanName: lite</CODE></PRE><P><STRONG>k8s/destination-binding.yaml</STRONG></P><PRE><CODE>apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: destination-service-binding
namespace: dev-space-neil
spec:
serviceInstanceName: destination-service</CODE></PRE><PRE><CODE>kubectl apply -f k8s/destination-instance.yaml
kubectl apply -f k8s/destination-binding.yaml</CODE></PRE><P>Wait until both show <CODE>Ready: True</CODE>:</P><PRE><CODE>kubectl get serviceinstance,servicebinding -n dev-space-neil</CODE></PRE><P>The binding creates a Kubernetes secret called <CODE>destination-service-binding</CODE> containing <CODE>clientid</CODE>, <CODE>clientsecret</CODE>, <CODE>url</CODE> (XSUAA token endpoint), and <CODE>uri</CODE> (Destination Service endpoint). Your container will read these as environment variables.</P><HR /><H2 id="toc-hId-783957031">Step 3: Create the BTP Destination via REST API</H2><P>If you have access to the BTP cockpit, create the destination there as described in the original post. If not, use the Destination Service REST API directly ā the service binding credentials you just created have enough access to create instance-level destinations.</P><P>Get a token and POST the destination:</P><PRE><CODE>TOKEN=$(kubectl get secret destination-service-binding -n dev-space-neil \
-o jsonpath='{.data.clientid}' | base64 -d)
SECRET=$(kubectl get secret destination-service-binding -n dev-space-neil \
-o jsonpath='{.data.clientsecret}' | base64 -d)
XSUAA=$(kubectl get secret destination-service-binding -n dev-space-neil \
-o jsonpath='{.data.url}' | base64 -d)
URI=$(kubectl get secret destination-service-binding -n dev-space-neil \
-o jsonpath='{.data.uri}' | base64 -d)
ACCESS_TOKEN=$(curl -s -X POST "$XSUAA/oauth/token" \
-u "$TOKEN:$SECRET" \
-d "grant_type=client_credentials" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
curl -X POST "$URI/destination-configuration/v1/instanceDestinations" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Name": "S4H_SANDBOX_BP",
"Type": "HTTP",
"URL": "https://sandbox.api.sap.com/s4hanacloud/sap/opu/odata/sap/API_BUSINESS_PARTNER",
"Authentication": "NoAuthentication",
"ProxyType": "Internet",
"APIKey": "<your-api.sap.com-key>"
}'</CODE></PRE><P>The distinction between <CODE>subaccountDestinations</CODE> and <CODE>instanceDestinations</CODE> matters here. Subaccount destinations are managed at the BTP subaccount level (requires admin access). Instance destinations are scoped to the service instance and can be created by the binding credentials ā which is what we have.</P><HR /><H2 id="toc-hId-587443526">Step 4: Write the Handler and HTTP Server</H2><P>The business logic in <CODE>handler.js</CODE> is unchanged from the original post ā get a token, resolve the destination, call the API. The only difference is gzip handling (the SAP API sandbox always compresses responses) and reading the API key from the destination properties rather than a separate Kubernetes secret.</P><P><STRONG>handler.js</STRONG></P><PRE><CODE>const https = require("https");
const http = require("http");
const zlib = require("zlib");
/**
* Gets an OAuth2 access token from XSUAA using client credentials flow.
* Credentials are injected as env vars from the destination-service-binding secret.
*/
async function getDestinationToken() {
const credentials = Buffer.from(
`${process.env.CLIENTID}:${process.env.CLIENTSECRET}`
).toString("base64");
const tokenUrl = new URL(`${process.env.XSUAA_URL}/oauth/token`);
return new Promise((resolve, reject) => {
const options = {
hostname: tokenUrl.hostname,
path: `${tokenUrl.pathname}?grant_type=client_credentials`,
method: "POST",
headers: {
Authorization: `Basic ${credentials}`, // Base64-encoded clientid:clientsecret
"Content-Type": "application/x-www-form-urlencoded",
},
};
const req = https.request(options, (res) => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => {
try { resolve(JSON.parse(data).access_token); }
catch (e) { reject(new Error(`Token parse failed: ${data}`)); }
});
});
req.on("error", reject);
req.end();
});
}
/**
* Resolves a named BTP Destination via the Destination Service REST API.
* Returns the full destination object including destinationConfiguration,
* which contains the target URL and any additional properties (e.g. APIKey).
*/
async function getDestination(name, token) {
const destUrl = new URL(
`${process.env.DESTINATION_URI}/destination-configuration/v1/destinations/${name}`
);
return new Promise((resolve, reject) => {
const options = {
hostname: destUrl.hostname,
path: destUrl.pathname,
method: "GET",
headers: { Authorization: `Bearer ${token}` }, // Token obtained from getDestinationToken()
};
const req = https.request(options, (res) => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => {
try { resolve(JSON.parse(data)); }
catch (e) { reject(new Error(`Destination parse failed: ${data}`)); }
});
});
req.on("error", reject);
req.end();
});
}
/**
* Calls the SAP Business Partner API via the resolved destination.
* The SAP API Hub sandbox always returns gzip-compressed responses,
* so we explicitly handle gzip/deflate decompression via Node's zlib module.
*/
async function fetchBusinessPartners(destination) {
const config = destination.destinationConfiguration;
const targetUrl = new URL(`${config.URL}/A_BusinessPartner?$format=json&$top=20`);
// Use http or https depending on the destination URL scheme
const protocol = targetUrl.protocol === "https:" ? https : http;
return new Promise((resolve, reject) => {
const options = {
hostname: targetUrl.hostname,
path: `${targetUrl.pathname}${targetUrl.search}`,
method: "GET",
headers: {
APIKey: config.APIKey, // API key stored as a destination property, not a separate secret
Accept: "application/json",
},
};
const req = protocol.request(options, (res) => {
const chunks = [];
const encoding = res.headers["content-encoding"];
// Pipe through the appropriate decompressor based on Content-Encoding header
const stream =
encoding === "gzip" ? res.pipe(zlib.createGunzip()) :
encoding === "deflate" ? res.pipe(zlib.createInflate()) : res;
stream.on("data", (chunk) => chunks.push(chunk));
stream.on("end", () => {
const raw = Buffer.concat(chunks).toString("utf8");
try { resolve({ statusCode: res.statusCode, body: JSON.parse(raw) }); }
catch (e) { resolve({ statusCode: res.statusCode, body: raw }); }
});
stream.on("error", reject);
});
req.on("error", reject);
req.end();
});
}
/**
* Main entry point ā orchestrates the full call chain:
* 1. Get XSUAA token
* 2. Resolve the S4H_SANDBOX_BP destination
* 3. Fetch Business Partners from the SAP API sandbox
* Returns a response object compatible with both Kyma Functions and the HTTP server wrapper.
*/
module.exports = {
main: async function (event, context) {
try {
const token = await getDestinationToken();
const destination = await getDestination("S4H_SANDBOX_BP", token);
const result = await fetchBusinessPartners(destination);
return {
statusCode: result.statusCode,
headers: { "Content-Type": "application/json" },
body: result.body,
};
} catch (err) {
console.error("Error:", err.message);
return {
statusCode: 500,
headers: { "Content-Type": "application/json" },
body: { error: err.message },
};
}
},
};</CODE></PRE><P>Since this is a container rather than a Kyma Function, we need a simple HTTP server to drive it. <CODE>server.js</CODE> wraps the handler:</P><P><STRONG>server.js</STRONG></P><PRE><CODE>const http = require("http");
const handler = require("./handler");
const PORT = process.env.PORT || 8080; // Default to 8080; override via env var if needed
/**
* Minimal HTTP server that wraps handler.js for container deployment.
* Kyma Functions have their own runtime that calls handler.main() directly ā
* this server provides the equivalent entry point when running as a standard Deployment.
* Every request regardless of path/method triggers the Business Partner fetch.
*/
const server = http.createServer(async (req, res) => {
try {
const result = await handler.main({}, {});
// handler.main() may return body as object or string ā normalise to string
const body = typeof result.body === "string"
? result.body
: JSON.stringify(result.body);
res.writeHead(result.statusCode || 200, {
"Content-Type": "application/json",
...result.headers,
});
res.end(body);
} catch (err) {
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: err.message }));
}
});
server.listen(PORT, () => console.log(`Listening on port ${PORT}`));</CODE></PRE><HR /><H2 id="toc-hId-390930021">Step 5: Build and Push a Multi-Platform Container Image</H2><P>This step doesn't exist in the original Kyma Function approach ā the serverless runtime handles that for you. Here you need to build a Docker image and push it to a registry the cluster can reach.</P><P><STRONG>Dockerfile</STRONG></P><PRE><CODE>FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
COPY handler.js server.js ./
EXPOSE 8080
CMD ["node", "server.js"]</CODE></PRE><P>One important detail: if you're on an Apple Silicon Mac, Docker builds <CODE>linux/arm64</CODE> by default. Most cloud Kubernetes clusters run <CODE>linux/amd64</CODE>. Build for both to avoid an <CODE>ImagePullBackOff</CODE> with the error <CODE>no match for platform in manifest</CODE>:</P><PRE><CODE># Authenticate with GitHub Container Registry
echo $(gh auth token) | docker login ghcr.io -u <your-github-username> --password-stdin
# Build and push for both platforms
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t ghcr.io/<your-github-username>/bp-function:latest \
--push .</CODE></PRE><P>Make the package public in GitHub (your profile ā Packages ā bp-function ā Package settings ā Change visibility ā Public) so the cluster can pull it without credentials.</P><HR /><H2 id="toc-hId-194416516">Step 6: Deploy to Kyma</H2><P><STRONG>k8s/deployment.yaml</STRONG></P><PRE><CODE>apiVersion: apps/v1
kind: Deployment
metadata:
name: bp-function
namespace: dev-space-neil
labels:
app: bp-function
spec:
replicas: 1
selector:
matchLabels:
app: bp-function
template:
metadata:
labels:
app: bp-function
spec:
containers:
- name: bp-function
image: ghcr.io/<your-github-username>/bp-function:latest
ports:
- containerPort: 8080
env:
- name: CLIENTID
valueFrom:
secretKeyRef:
name: destination-service-binding
key: clientid
- name: CLIENTSECRET
valueFrom:
secretKeyRef:
name: destination-service-binding
key: clientsecret
- name: XSUAA_URL
valueFrom:
secretKeyRef:
name: destination-service-binding
key: url
- name: DESTINATION_URI
valueFrom:
secretKeyRef:
name: destination-service-binding
key: uri
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: bp-function
namespace: dev-space-neil
spec:
selector:
app: bp-function
ports:
- port: 80
targetPort: 8080</CODE></PRE><P><STRONG>k8s/apirule.yaml</STRONG></P><P>Use the full cluster domain rather than a short hostname. Find yours with:</P><PRE><CODE>kubectl get configmap shoot-info -n kube-system -o jsonpath='{.data.domain}'</CODE></PRE><PRE><CODE>apiVersion: gateway.kyma-project.io/v2
kind: APIRule
metadata:
name: bp-function
namespace: dev-space-neil
spec:
hosts:
- bp-function.<your-cluster-domain>.kyma.ondemand.com
service:
name: bp-function
port: 80
gateway: kyma-system/kyma-gateway
rules:
- path: /*
methods:
- GET
noAuth: true</CODE></PRE><P>Apply both:</P><PRE><CODE>kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/apirule.yaml</CODE></PRE><P>Check the pod is running:</P><PRE><CODE>kubectl get pods -n dev-space-neil -l app=bp-function</CODE></PRE><HR /><H2 id="toc-hId--2096989">Step 7: Test</H2><PRE><CODE>curl https://bp-function.<your-cluster-domain>.kyma.ondemand.com</CODE></PRE><P>You should see Business Partner records:</P><PRE><CODE>{
"d": {
"results": [
{
"BusinessPartner": "11",
"BusinessPartnerFullName": "Cust15 Cust15",
...
}
]
}
}</CODE></PRE><HR /><H2 id="toc-hId-148643863">How It Works</H2><P>The call chain is the same as the original Kyma Function approach:</P><PRE><CODE>Internet -> APIRule (Istio) -> Service -> Pod
-> XSUAA (get OAuth token)
-> Destination Service (resolve S4H_SANDBOX_BP)
-> SAP API Hub sandbox (fetch Business Partners)
-> JSON response</CODE></PRE><P>The difference is that your code runs in a container you built and pushed, rather than in the Kyma serverless runtime. The BTP Service Operator still injects the Destination Service credentials into the pod as environment variables via the ServiceBinding secret ā that part is identical.</P><HR /><H2 id="toc-hId--47869642">Key Points</H2><P><STRONG>The Serverless module is optional.</STRONG> The BTP Service Operator, APIRule, and Istio are the parts that matter for this integration pattern. If Serverless isn't enabled, a standard Deployment works just as well.</P><P><STRONG>Instance destinations vs subaccount destinations.</STRONG> If you can't create destinations in the BTP cockpit, use <CODE>POST /destination-configuration/v1/instanceDestinations</CODE>. These are scoped to the service instance and can be created using the binding credentials. They're resolved at runtime just like subaccount destinations.</P><P><STRONG>Multi-platform builds matter.</STRONG> Apple Silicon Macs build <CODE>linux/arm64</CODE> by default. Use <CODE>docker buildx build --platform linux/amd64,linux/arm64</CODE> to produce a manifest list that works on both architectures ā avoiding the cryptic <CODE>no match for platform in manifest</CODE> error.</P><P><STRONG>Use the full APIRule hostname.</STRONG> In Kyma API Gateway v2, short hostnames in the <CODE>hosts</CODE> field expand to include the namespace (<CODE>bp-function.dev-space-neil.<cluster-domain></CODE>). This may not match your cluster's wildcard DNS. Use the full explicit hostname (<CODE>bp-function.<cluster-domain></CODE>) to be safe ā check what format existing APIRules in your cluster use.</P><P><STRONG>Gzip is not optional.</STRONG> The SAP API Business Hub sandbox always returns gzip-compressed responses regardless of what you put in <CODE>Accept-Encoding</CODE>. Handle it explicitly with Node's built-in <CODE>zlib</CODE> module.</P>2026-04-02T10:12:48.110000+02:00https://community.sap.com/t5/sap-for-utilities-blog-posts/the-utility-clean-core-blueprint-how-sap-btp-enables-modernization-without/ba-p/14366797The Utility Clean Core Blueprint: How SAP BTP Enables Modernization Without Disrupting Regulated OPR2026-04-07T13:31:11.299000+02:00Atul_Joshi85https://community.sap.com/t5/user/viewprofilepage/user-id/2274193<P> </P><H1 id="toc-hId-1664257766"><SPAN>The Utility Clean Core Blueprint: How SAP BTP Enables Modernization Without Disrupting</SPAN> Regulated Operations</H1><P><EM> </EM></P><P><EM>A White Paper by Atul Joshi </EM></P><P><EM> </EM></P><H1 id="toc-hId-1467744261">Executive Summary</H1><P>Utilities today face a paradox: they must modernize rapidly to support digital customer expectations, DER integration, and grid intelligence ā yet they operate in one of the most regulated, riskāaverse environments in the world. Traditional SAP landscapes, especially SAP ISāU, are burdened with decades of custom code, pointātoāpoint integrations, and rigid processes that make transformation slow, expensive, and operationally risky.</P><P>This white paper introduces <STRONG>The Utility Clean Core Blueprint</STRONG>, a modernization model that enables utilities to evolve their SAP landscape without destabilizing missions, critical billing, metering, and customer operations. The blueprint leverages <STRONG>SAP Business Technology Platform (BTP)</STRONG> as the innovation and integration layer, allowing utilities to decouple custom logic, orchestrate processes, and adopt cloud capabilities ā all while keeping the SAP core stable, compliant, and upgradeāready.</P><P> </P><OL><LI><SPAN>The Modernization Challenge in Regulated Utilities</SPAN></LI></OL><P> </P><P>Utilities operate under unique constraints:</P><UL><LI><STRONG>Regulatory oversight</STRONG> limits downtime and process changes.</LI><LI><STRONG>Highāvolume billing cycles</STRONG> require predictable system performance.</LI><LI><STRONG>Legacy customizations</STRONG> make upgrades risky and expensive.</LI><LI><STRONG>Aging ISāU systems</STRONG> cannot support modern digital experiences.</LI><LI><STRONG>Integration sprawl</STRONG> slows innovation and increases operational risk.</LI></UL><P>Most utilities want to move toward S/4HANA Utilities, but the path is blocked by:</P><UL><LI>10ā20 years of Zācode</LI><LI>tightly coupled interfaces</LI><LI>custom workflows embedded in ISāU</LI><LI>inflexible monolithic architecture</LI></UL><P>This is where <STRONG>Clean Core</STRONG> becomes not just a best practice ā but a survival strategy.</P><H1 id="toc-hId-1271230756">2. What Clean Core Really Means for Utilities</H1><P> </P><P>Clean Core is often misunderstood as āremoving custom code.ā For utilities, it means something deeper:</P><P><STRONG>Clean Core = A stable SAP transactional engine + an agile innovation layer on SAP BTP</STRONG></P><P>This approach allows:</P><UL><LI>predictable billing and metering operations</LI><LI>faster innovation cycles</LI><LI>safer upgrades</LI><LI>cloudāready architecture</LI><LI>regulatory compliance</LI></UL><P>In other words: <STRONG>Keep the core clean. Move the complexity out. Modernize without breaking operations.</STRONG></P><H1 id="toc-hId-1074717251">3. SAP BTP: The Enabler of Utility Modernization</H1><P> </P><P>SAP BTP provides the capabilities utilities need to modernize safely:</P><P><STRONG> </STRONG></P><P><STRONG>Key BTP Services for Utilities</STRONG></P><TABLE><TBODY><TR><TD><P><STRONG>Capability</STRONG></P></TD><TD><P><STRONG>BTP Service</STRONG></P></TD><TD><P><STRONG>Utility Value</STRONG></P></TD></TR><TR><TD><P><STRONG>Event-driven decoupling</STRONG></P></TD><TD><P><STRONG>Event Mesh</STRONG></P></TD><TD><P>Real-time meter events, outage events, billing triggers</P></TD></TR><TR><TD><P><STRONG>Integration modernization</STRONG></P></TD><TD><P><STRONG>Integration Suite</STRONG></P></TD><TD><P>Replace PI/PO, eliminate point-to-point interfaces</P></TD></TR><TR><TD><P><STRONG>Extension development</STRONG></P></TD><TD><P><STRONG>SAP CAP / Kyma</STRONG></P></TD><TD><P>Move Zālogic out of ISāU</P></TD></TR><TR><TD><P><STRONG>Workflow automation</STRONG></P></TD><TD><P><STRONG>BTP Workflow</STRONG></P></TD><TD><P>Field service approvals, credit workflows</P></TD></TR><TR><TD><P><STRONG>Data intelligence</STRONG></P></TD><TD><P><STRONG>HANA Cloud</STRONG></P></TD><TD><P>Meter analytics, billing insights</P></TD></TR><TR><TD><P><STRONG>Security & governance</STRONG></P></TD><TD><P><STRONG>IAS/IPS</STRONG></P></TD><TD><P>Unified identity for customer and employee apps</P></TD></TR></TBODY></TABLE><P>BTP becomes the <STRONG>innovation shell</STRONG> around the SAP core.</P><P><STRONG> </STRONG></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="atul_joshi85_0-1775508453976.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/393846i4449917AB93FD3AF/image-size/medium?v=v2&px=400" role="button" title="atul_joshi85_0-1775508453976.png" alt="atul_joshi85_0-1775508453976.png" /></span></P><P> </P><H1 id="toc-hId-878203746">4. The Utility Clean Core Blueprint</H1><P> </P><P>The Utility Clean Core Blueprint consists of five practical steps:</P><H2 id="toc-hId-810772960">Step 1 ā Stabilize the Core</H2><UL><LI>Freeze custom development in ISāU</LI><LI>Identify Zāobjects that block upgrades</LI><LI>Classify custom code into: retire, refactor, or externalize</LI><LI>Clean up unused interfaces</LI></UL><H2 id="toc-hId-614259455">Step 2 ā Externalize Custom Logic to BTP</H2><P> </P><P>Move the following out of ISāU:</P><UL><LI>complex billing rules</LI><LI>validation logic</LI><LI>workflow approvals</LI><LI>customer-facing processes</LI><LI>meter data transformations</LI></UL><P>Use CAP, Node.js, or Java on BTP.</P><H2 id="toc-hId-417745950">Step 3 ā Modernize Integrations</H2><P>Replace:</P><UL><LI>PI/PO mappings</LI><LI>RFC-based interfaces</LI><LI>batch jobs</LI></UL><P>With:</P><UL><LI>Event Mesh</LI><LI>Integration Suite</LI><LI>APIs</LI></UL><H2 id="toc-hId-221232445">Step 4 ā Introduce Event-Driven Architecture</H2><P>Trigger events for:</P><UL><LI>meter reads</LI><LI>billing completion</LI><LI>move-in/move-out</LI><LI>credit actions</LI><LI>outage notifications</LI></UL><P>This decouples processes and reduces core load.</P><H2 id="toc-hId-24718940">Step 5 ā Enable Coexistence with S/4HANA</H2><P>Run ISāU + S/4HANA + BTP in hybrid mode:</P><UL><LI>ISāU remains the billing engine</LI><LI>S/4 handles finance, procurement, asset management</LI><LI>BTP orchestrates innovation</LI></UL><P>This avoids a risky ābig bangā migration.</P><H1 id="toc-hId-468862799">5. Practical Utility Case Studies (Realistic, Practitioner-Level)</H1><H3 id="toc-hId--314456720">Case Study 1: Reducing Billing Run Failures with BTP Event Mesh</H3><P>A North American utility struggled with billing run failures caused by custom validations embedded in ISāU.</P><P><STRONG><U>Problem:</U></STRONG> Every enhancement point slowed down billing and caused unpredictable dumps. <STRONG><U>Solution:</U></STRONG></P><UL><LI>Move validation logic to a CAP service on BTP</LI><LI>Trigger validation via Event Mesh before billing</LI><LI>Return results via API</LI></UL><P><STRONG><U>Outcome:</U></STRONG></P><UL><LI>Billing runtime reduced by 27%</LI><LI>Zero dumps in 3 months</LI><LI>Clean Core achieved without touching core billing logic</LI></UL><H3 id="toc-hId--510970225"><STRONG>Case Study 2: </STRONG>Modernizing<STRONG> Move-In/Move-Out Without Touching ISāU</STRONG></H3><P> </P><P>A regulated utility needed a digital customer onboarding experience.</P><P><STRONG>Problem:</STRONG> ISāU move-in/out processes were too rigid and heavily customized.</P><P><STRONG>Solution:</STRONG></P><UL><LI>Build a customer onboarding app on BTP</LI><LI>Orchestrate workflow using BTP Workflow</LI><LI>Trigger ISāU move-in via API only at the final step</LI></UL><P><STRONG>Outcome:</STRONG></P><UL><LI>Customer onboarding time reduced from 3 days to 30 minutes</LI><LI>No changes required in ISāU core</LI><LI>Regulatory compliance maintained</LI></UL><P><STRONG> </STRONG></P><P><STRONG>Case Study 3: Eliminating 40+ Point-to-Point Integrations</STRONG></P><P>Problem: European utility had PI/PO interfaces that were expensive to maintain.</P><P><STRONG>Solution:</STRONG></P><UL><LI>Replace PI/PO with Integration Suite</LI><LI>Introduce canonical data models</LI><LI>Use Event Mesh for asynchronous processes</LI></UL><P><STRONG>Outcome:</STRONG></P><UL><LI>Integration failures reduced by 60%</LI><LI>Upgrade downtime reduced by 40%</LI><LI>IT operations cost reduced significantly</LI></UL><P><STRONG> </STRONG></P><H1 id="toc-hId--120677716">6. The Coexistence Architecture: ISāU + S/4 + BTP</H1><P>Utilities cannot afford a big-bang migration. The coexistence model allows:</P><P><STRONG>ISāU</STRONG></P><P>ā Billing, metering, device management</P><P><STRONG>S/4HANA</STRONG></P><P>ā Finance, procurement, asset management</P><P><STRONG>SAP BTP</STRONG></P><P>ā Innovation, integration, automation, analytics</P><P>This architecture supports:</P><UL><LI>gradual modernization</LI><LI>regulatory stability</LI><LI>continuous innovation</LI></UL><H1 id="toc-hId--317191221">7. Governance: The Missing Piece in Most Utility Programs</H1><P>Clean Core is not a technical exercise ā it is a governance discipline.</P><P><STRONG>Key governance principles</STRONG></P><UL><LI>No custom code in the core</LI><LI>All new extensions must be BTP-first</LI><LI>Event-driven architecture preferred over batch</LI><LI>API-led integration</LI><LI>Quarterly review of Zāobjects</LI><LI>Architecture board approval for all enhancements</LI></UL><P>This is how utilities stay modern <EM>after</EM> the transformation.</P><H1 id="toc-hId--513704726">8. Conclusion: Modernize Without Disruption</H1><P>Utilities cannot pause operations to modernize. They need a blueprint that:</P><UL><LI>protects regulated processes</LI><LI>reduces technical debt</LI><LI>accelerates innovation</LI><LI>prepares for S/4HANA</LI><LI>supports the energy transition</LI></UL><P><STRONG> </STRONG></P><P><STRONG>The Utility Clean Core Blueprint</STRONG> provides exactly that.</P><P> </P><P>By using SAP BTP as the innovation layer, utilities can modernize safely, incrementally, and intelligently ā without destabilizing the mission-critical systems that keep the lights on.</P><P> </P>2026-04-07T13:31:11.299000+02:00https://community.sap.com/t5/technology-blog-posts-by-sap/configure-kyma-to-work-with-akamai-cdn/ba-p/14372412Configure Kyma to work with Akamai CDN2026-04-21T08:38:31.650000+02:00mlukhttps://community.sap.com/t5/user/viewprofilepage/user-id/1601022<P><FONT face="arial,helvetica,sans-serif">The article describes how to configure SAP BTP, Kyma runtime to work with Content Delivery Network (CDN) such as Akamai CDN.</FONT></P><P><FONT face="arial,helvetica,sans-serif">A Content Delivery Network consists of multiple servers placed around the world. These servers, called edge servers, are responsible for caching website content and serving it to end users from the nearest location. The original content lives in the 'origin server', which is Kyma in our case.</FONT></P><P data-unlink="true"><FONT face="arial,helvetica,sans-serif">The configuration that defines how Akamai edge servers handle traffic for a given domain is called a 'Property'. It maps hostnames (for example, <A href="http://www.example.com" target="_blank" rel="noopener nofollow noreferrer">www.example.com</A>) to specific edge behaviors, such as caching rules, security settings, and origin server communication.</FONT></P><P><FONT face="arial,helvetica,sans-serif">Akamai edge servers also support TLS termination, so they act as endpoints for HTTPs connections and serve TLS certificates for external domains.</FONT></P><P><FONT face="arial,helvetica,sans-serif">A single Kyma instance can serve content for multiple domains (multiple Akamai Properties), which may then be redirected to any workloads. It is visualized in the following diagram:</FONT></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="cdn.drawio.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/398640iF75AA472A505A818/image-size/large?v=v2&px=999" role="button" title="cdn.drawio.png" alt="cdn.drawio.png" /></span>ā</P><P><FONT face="arial,helvetica,sans-serif">As we can see, there are two Akamai Properties, each with two host names. We want to redirect requests for all external host names to the same Kyma instance, ab1234.kyma.ondemand.com, with the IP address 203.0.113.50. At this point, we need a host name on the Kyma side that would accept the traffic. <SPAN>To keep things simple,</SPAN> we may use a subdomain of the Kyma cluster domain, like cdn.ab1234.kyma.ondemand.com, for which we can easily register a DNS entry and generate a TLS certificate.</FONT></P><P>In the Akamai Property, you need to configure a rule in the following way:</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="akamai-property-config.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/397100i2838DDE4A73D4147/image-size/large?v=v2&px=999" role="button" title="akamai-property-config.png" alt="akamai-property-config.png" /></span></P><P>Let's now imagine that an end user makes a request to the domain <A href="http://www.foo.example.com" target="_blank" rel="noopener nofollow noreferrer">www.foo.example.com</A>, configured as in the above screenshot. In such a case, the Akamai edge server acts as a reverse proxy and contacts the Kyma origin server in the following way:</P><UL><LI>Connection is technically made to the origin server defined in the 'Origin Server Hostname', so the edge server resolves the DNS name <FONT face="arial,helvetica,sans-serif">cdn.ab1234.kyma.ondemand.com</FONT> and connects to the IP address 203.0.113.50.</LI><LI>In Layer 4, the SNI host name points to the external domain <A href="http://www.foo.example.com" target="_blank" rel="noopener nofollow noreferrer">www.foo.example.com</A>.</LI><LI>In Layer 7, the Host header also points to the external domain <A href="http://www.foo.example.com" target="_blank" rel="noopener nofollow noreferrer">www.foo.example.com</A>.</LI><LI>It is expected that the origin server provides a valid TLS certificate with a Common Name or Subject Alternative Name equal to the origin hostname, which is <FONT face="arial,helvetica,sans-serif">cdn.ab1234.kyma.ondemand.com</FONT>.</LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="akamai2.drawio.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/398603i11011793FF256ABF/image-size/large?v=v2&px=999" role="button" title="akamai2.drawio.png" alt="akamai2.drawio.png" /></span></P><P>To accept such traffic on the Kyma side, we need a custom Istio Gateway:</P><UL><LI>With internal domain:<UL><LI>cdn.ab1234.kyma.ondemand.com</LI></UL></LI><LI>With external domains:<UL><LI>Either <A href="http://www.foo.example.com" target="_blank" rel="noopener nofollow noreferrer">www.foo.example.com</A>, api.foo.example.com, <A href="http://www.bar.example.com" target="_blank" rel="noopener nofollow noreferrer">www.bar.example.com</A>, <A href="http://www.bar.example.com" target="_blank" rel="noopener nofollow noreferrer">www.bar.example.com</A>, m.bar.example.com</LI><LI>Or *.foo.example.com, *.bar.example.com</LI><LI>Or *.example.com</LI></UL></LI><LI>Pointing to secret with a key and a certificate for internal domain cdn.ab1234.kyma.ondemand.com.</LI></UL><P>Note that the Gateway above doesn't need a TLS certificate for external domains, which is hosted by the CDN. The Akamai edge server expects the certificate to be valid for the origin hostname.</P><P>To create such a configuration, follow this example:</P><P>1. Create three resources: DNSEntry, Certificate, and Istio Gateway.</P><pre class="lia-code-sample language-bash"><code>kubectl create ns gateway-ns</code></pre><pre class="lia-code-sample language-yaml"><code>apiVersion: dns.gardener.cloud/v1alpha1
kind: DNSEntry
metadata:
name: cdn-internal-domain-dnsentry
namespace: gateway-ns
annotations:
dns.gardener.cloud/class: garden
spec:
dnsName: "cdn.ab1234.kyma.ondemand.com"
ttl: 600
targets:
- 203.0.113.50 # Load Balancer IP address</code></pre><pre class="lia-code-sample language-yaml"><code>apiVersion: cert.gardener.cloud/v1alpha1
kind: Certificate
metadata:
name: cdn-internal-domain-certificate
namespace: istio-system
spec:
secretName: cdn-internal-domain-certificate
commonName: "cdn.ab1234.kyma.ondemand.com"
issuerRef:
name: garden</code></pre><pre class="lia-code-sample language-yaml"><code>apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: cdn-gateway
namespace: gateway-ns
spec:
selector:
app: istio-ingressgateway
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: cdn-internal-domain-certificate
hosts:
- "cdn.ab1234.kyma.ondemand.com"
- "*.example.com"</code></pre><P>2. Deploy an application.</P><pre class="lia-code-sample language-bash"><code>kubectl create ns application
kubectl label namespace application istio-injection=enabled --overwrite</code></pre><pre class="lia-code-sample language-yaml"><code>apiVersion: v1
kind: ServiceAccount
metadata:
name: httpbin
namespace: application
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
namespace: application
labels:
app: httpbin
service: httpbin
spec:
ports:
- name: http
port: 8000
targetPort: 80
selector:
app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
namespace: application
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v1
template:
metadata:
labels:
app: httpbin
version: v1
spec:
serviceAccountName: httpbin
containers:
- image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
ports:
- containerPort: 80</code></pre><P>3. Create an APIRule resource, which exposes the above application via the previously created Gateway and on a particular external domain:</P><pre class="lia-code-sample language-abap"><code>apiVersion: gateway.kyma-project.io/v2
kind: APIRule
metadata:
name: application-foo-external-domain
namespace: application
spec:
gateway: gateway-ns/cdn-gateway
hosts:
- "www.foo.example.com"
rules:
- methods:
- GET
noAuth: true
path: /*
service:
name: httpbin
port: 8000</code></pre><P> 4. For each external domain, create a separate APIRule resource: </P><pre class="lia-code-sample language-abap"><code>apiVersion: gateway.kyma-project.io/v2
kind: APIRule
metadata:
name: application-bar-external-domain
namespace: application
spec:
gateway: gateway-ns/cdn-gateway
hosts:
- "www.bar.example.com"
rules:
- methods:
- GET
noAuth: true
path: /*
service:
name: httpbin
port: 8000</code></pre><P>5. To test this configuration, we need to simulate what the Akamai edge server does:</P><UL><LI>Make a TLS connection to the internal domain cdn.ab1234.kyma.ondemand.com</LI><LI>Use SNI host name pointing to external domain <A href="http://www.foo.example.com" target="_blank" rel="noopener nofollow noreferrer">www.foo.example.com</A></LI><LI>Provide an HTTP Host header pointing to the external domain <A href="http://www.foo.example.com" target="_blank" rel="noopener nofollow noreferrer">www.foo.example.com</A></LI></UL><P>You can use the following bash command:</P><pre class="lia-code-sample language-bash"><code>(
echo -ne "GET /headers HTTP/1.1\r\n"
echo -ne "Host: www.foo.example.com\r\n"
echo -ne "Connection: close\r\n\r\n"
) | openssl s_client -connect "cdn.ab1234.kyma.ondemand.com:443" -servername "www.foo.example.com" -quiet</code></pre><P>6. If this works, your Kyma instance is prepared. Once the Akamai Property configuration is applied, try to make a request via the external domain:</P><pre class="lia-code-sample language-bash"><code>curl "https://www.foo.example.com/headers"</code></pre><P> </P>2026-04-21T08:38:31.650000+02:00https://community.sap.com/t5/technology-blog-posts-by-sap/n8n-on-sap-btp-kyma-part-1-from-local-docker-to-enterprise-grade-deployment/ba-p/14385313n8n on SAP BTP Kyma ā Part 1: From Local Docker to Enterprise-Grade Deployment2026-05-13T11:24:38.536000+02:00Shopithan_Goneswaranhttps://community.sap.com/t5/user/viewprofilepage/user-id/2176905<P> </P><DIV class=""><DIV class=""><SPAN class="">Technology Blogs</SPAN> <SPAN class="">|</SPAN> <SPAN>SAP Community</SPAN> <SPAN class="">|</SPAN> <SPAN>n8n meets SAP ā Part 1 of 3</SPAN></DIV><DIV class=""><H1 id="toc-hId-1666070921"><STRONG>Enterprise Automation ā Deploying n8n on SAP BTP Kyma</STRONG></H1><P class=""><STRONG>A Step-by-Step Guide to Hosting n8n in a Cloud-Native SAP Environment</STRONG></P><DIV class=""><STRONG>Technology Blogs <SPAN class="">Ā·</SPAN> n8n Ā· SAP BTP Ā· Kyma Ā· Kubernetes</STRONG></DIV><DIV class=""> </DIV></DIV><STRONG><!-- Series nav --></STRONG><DIV class=""><DIV class=""><STRONG>Series Overview:</STRONG></DIV><OL><LI><STRONG>Enterprise Automation: Deploying n8n on SAP BTP Kyma <span class="lia-unicode-emoji" title=":round_pushpin:">š</span></STRONG></LI><LI><STRONG><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/create-a-custom-sap-ai-core-chat-model-node-in-n8n/ba-p/14390936" target="_self">Create a Custom SAP AI Core Chat Model Node in n8n </A></STRONG></LI><LI><STRONG><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/workflow-n8n-integration-with-joule-studio/ba-p/14392294" target="_self">Workflow n8n Integration with Joule Studio</A></STRONG></LI></OL></DIV><STRONG><!-- āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā --></STRONG><H2 id="toc-hId-1598640135"><STRONG><SPAN class="">1. </SPAN>The Big Picture</STRONG></H2><P>If you have ever run n8n on a local machine or a simple VM, you already know how powerful it is. A few clicks, a running container, and you have a workflow automation engine at your fingertips. But when it comes to real enterprise use, shared access, uptime guarantees, integration with SAP systems, and data that cannot simply disappear when a server restarts, a more robust foundation is needed.</P><P>This is where SAP BTP, Kyma runtime comes in. Kyma is SAP's fully managed Kubernetes environment, running directly on the Business Technology Platform. Instead of you managing cluster infrastructure, SAP manages the control plane. You deploy workloads; SAP keeps the platform running. Your n8n instance gets all the benefits of Kubernetes ā automatic restarts on failure, persistent storage, a stable external URL, and native proximity to other BTP services like SAP AI Core or SAP HANA Cloud ā without the overhead of operating a cluster yourself.</P><P data-unlink="true"><SPAN>This post comes with all the files you need. At the bottom of this post you will find the complete contents of the deployment files, <A href="#toc-hId-14385313-h_3970629631241778598709429" target="_self" rel="nofollow noopener noreferrer">docker-compose.yml</A> for local development, and <A href="#toc-hId-14385313-h_8006257321721778598732082" target="_self" rel="nofollow noopener noreferrer">n8n-pvc.yaml</A> together with <A href="#toc-hId-14385313-h_8516816302171778598759145" target="_self" rel="nofollow noopener noreferrer">n8n-kyma.yaml</A> for the Kyma deployment. You can create each file locally by simply copying the code provided. By the end of this post, you will have a live, publicly accessible n8n instance running inside your SAP BTP subaccount.</SPAN></P><P> </P><H2 id="toc-hId-1402126630"><SPAN class="">2. </SPAN>Provisioning the Infrastructure</H2><H3 id="toc-hId-1334695844">Enabling Kyma Runtime in SAP BTP Cockpit</H3><P>Before any deployment can happen, the Kyma runtime needs to be enabled in your SAP BTP subaccount. If you have not done this yet, the process is straightforward.</P><P>Log in to the <STRONG>SAP BTP Cockpit</STRONG> and navigate to your subaccount. In the left-hand navigation, open <STRONG>"Kyma Environment"</STRONG>. If Kyma has not been provisioned yet, you will see an <STRONG>"Enable Kyma"</STRONG> button. Click it, choose a plan (for most scenarios the default plan is sufficient), and confirm. Provisioning takes several minutes ā SAP is standing up a fully managed Kubernetes cluster on your behalf.</P><P>Once complete, the status in the Cockpit changes to <STRONG>"Created"</STRONG> and a <STRONG>"Link to dashboard"</STRONG> appears. That dashboard is your Kyma control plane ā a Kubernetes-native UI where you can inspect namespaces, deployments, services, and more. Keep it open alongside your terminal throughout this guide.</P><P> </P><P> </P><DIV class=""><DIV class=""><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="SAP BTP Cockpit subaccount view showing the Kyma Environment tile with status "Created" and the "Link to dashboard" button visible." style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/406280iFAF03BFBA6F7A8B8/image-size/large?v=v2&px=999" role="button" title="shopithan28_0-1777967535798.png" alt="SAP BTP Cockpit subaccount view showing the Kyma Environment tile with status "Created" and the "Link to dashboard" button visible." /><span class="lia-inline-image-caption" onclick="event.preventDefault();">SAP BTP Cockpit subaccount view showing the Kyma Environment tile with status "Created" and the "Link to dashboard" button visible.</span></span><P> </P><P> </P></DIV></DIV><DIV class=""><DIV class=""> </DIV><DIV class=""> </DIV><DIV class=""> </DIV><DIV class=""> </DIV><DIV class=""> </DIV><DIV class=""><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="The Kyma Dashboard landing page after clicking through ā showing the cluster overview and the left-hand namespace selector." style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/404377i2AB10C43499E4028/image-size/large?v=v2&px=999" role="button" title="shopithan28_2-1777451356063.png" alt="The Kyma Dashboard landing page after clicking through ā showing the cluster overview and the left-hand namespace selector." /><span class="lia-inline-image-caption" onclick="event.preventDefault();">The Kyma Dashboard landing page after clicking through ā showing the cluster overview and the left-hand namespace selector.</span></span><P> </P><P> </P></DIV></DIV><H3 id="toc-hId-1138182339">A Note on Resource Sizing</H3><P>n8n is a Node.js application. It is not particularly memory-hungry at rest, but it spins up processes to execute workflows, and those processes can be CPU-intensive depending on what your workflows do. For a shared team instance handling moderate workflow loads, a cluster with at least <STRONG>2 vCPUs and 4 GB of memory</STRONG> available for your workloads is a comfortable starting point.</P><P>In Kyma's managed environment, the underlying node pool is managed by SAP. If you are on a trial account, be aware that trial clusters have resource limits. For any production or shared team usage, an enterprise subaccount with a properly sized instance type is the right choice. You can check available capacity in the Kyma Dashboard under the cluster's node details.</P><HR /><!-- āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā --><H2 id="toc-hId-812586115"><SPAN class="">3. </SPAN>Establishing the Connection</H2><P>Your cluster is running in the SAP cloud. Your deployment files are on your local machine. To bridge the two, you need two tools installed locally: <STRONG>kubectl</STRONG> (the Kubernetes command-line client) and a <STRONG>Docker engine</STRONG> (for building custom images ā relevant in Part 2 of this series).</P><H3 id="toc-hId-745155329">The Toolbelt</H3><P data-unlink="true"><STRONG>kubectl</STRONG> is the standard CLI for interacting with any Kubernetes cluster. Install it following the official guide for your operating system. On macOS, <CODE>brew install kubectl</CODE> is the fastest path. On Windows, the installer is available directly from the Kubernetes project.</P><P><STRONG>Docker Desktop</STRONG> covers both the Docker engine and an optional local Kubernetes cluster for testing. Download it from <STRONG>docker.com</STRONG>. For this post, Docker is only needed for the local <CODE>docker-compose</CODE> option and for Part 2 ā the Kyma deployment itself pulls images directly from Docker Hub.</P><H3 id="toc-hId-548641824">The Key to the Kingdom: Downloading your Kubeconfig</H3><P>kubectl does not know which cluster to talk to until you give it a configuration file ā the <STRONG>kubeconfig</STRONG>. This file contains the cluster's API endpoint URL, authentication credentials, and context information. Kyma makes it easy to download.</P><P>In the <STRONG>BTP Cockpit</STRONG>, select Kyma Enviroment and click the "<SPAN>KubeconfigURL" </SPAN>. Save the file somewhere accessible, for example <CODE>~/.kube/kyma-config.yaml</CODE>. Then tell kubectl to use it by setting the <CODE>KUBECONFIG</CODE> environment variable in your terminal:</P></DIV><pre class="lia-code-sample language-bash"><code>export KUBECONFIG=~/.kube/kyma-config.yaml</code></pre><P>For this to persist across terminal sessions, add the line to your shell profile (<CODE>.zshrc</CODE>, <CODE>.bashrc</CODE>, or equivalent).</P><H3 id="toc-hId-352128319">Verification</H3><P>With the kubeconfig set, run:</P><pre class="lia-code-sample language-bash"><code>kubectl get nodes</code></pre><P>If everything is configured correctly, you will see a list of the worker nodes in your Kyma cluster ā their names, status (<CODE>Ready</CODE>), and age. This confirms that your local machine is communicating with the SAP cloud cluster. If you see an authentication error instead, double-check that the <CODE>KUBECONFIG</CODE> variable is set in the correct terminal session.</P><DIV class=""><DIV class=""> </DIV></DIV><HR /><!-- āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā --><H2 id="toc-hId-26532095"><SPAN class="">4. </SPAN>Deploying the Application</H2><P>With the cluster accessible, it is time to deploy. The process has a deliberate order: <STRONG>storage first, secrets second, application third.</STRONG> Never the other way around ā a Pod that starts without its volume or credentials will crash and require manual intervention.</P><H3 id="toc-hId--116130060">Namespace Creation</H3><P>Kubernetes clusters are shared environments. Namespaces are the mechanism for creating logical isolation within a cluster ā separate resource quotas, separate access controls, separate network policies. Running n8n in its own <CODE>n8n</CODE> namespace keeps it cleanly separated from any other workloads you deploy later.</P><pre class="lia-code-sample language-bash"><code>kubectl create namespace n8n</code></pre><DIV class="">Always use a dedicated namespace. Deploying into <CODE>default</CODE> is a habit that causes confusion fast once you have more than one workload in a cluster.</DIV><H3 id="toc-hId--312643565">Persistence First</H3><P>n8n stores everything on disk: your workflow definitions, saved credentials, execution history, and configuration. Without a persistent volume, all of that disappears every time the Pod restarts ā and in Kubernetes, Pods restart. For updates, node maintenance, or any crash, the container is replaced with a fresh one. Without persistence, you start from zero every time.</P><P>The <A href="#toc-hId-14385313-h_8006257321721778598732082" target="_self" rel="nofollow noopener noreferrer"><CODE>n8n-pvc.yaml</CODE></A> file in the repository defines a <STRONG>PersistentVolumeClaim</STRONG> ā a request to the cluster for a dedicated 5 GB storage volume. Apply it first:</P><pre class="lia-code-sample language-bash"><code>kubectl apply -f n8n-pvc.yaml</code></pre><P>Kyma provisions the underlying storage automatically. You can verify it was created and is in <CODE>Bound</CODE> status with:</P><pre class="lia-code-sample language-bash"><code>kubectl get pvc -n n8n</code></pre><DIV class=""><DIV class=""> </DIV></DIV><H3 id="toc-hId--509157070">The Secret Sauce: The Encryption Key</H3><P>n8n encrypts all credentials it stores ā API keys, passwords, OAuth tokens ā using an encryption key that you provide via the <CODE>N8N_ENCRYPTION_KEY</CODE> environment variable. <STRONG>This key is critical.</STRONG> If you lose it, n8n can no longer decrypt any of the credentials you have saved, and you will need to re-enter them all from scratch.</P><P>Generate a secure key using:</P><pre class="lia-code-sample language-bash"><code>openssl rand -hex 32</code></pre><P>Copy the output. Now create a Kubernetes Secret to hold it safely ā secrets in Kubernetes are stored separately from your application manifests and are not exposed in plain text in your deployment files:</P><pre class="lia-code-sample language-bash"><code>kubectl create secret generic n8n-secret \
--from-literal=N8N_ENCRYPTION_KEY='<your-generated-key>' \
-n n8n</code></pre><DIV class=""><STRONG>Never</STRONG> put a real key directly into a YAML file that you commit to a repository. The <A href="#toc-hId-14385313-h_4726520522591778598820676" target="_self" rel="nofollow noopener noreferrer"><CODE>.env.example</CODE></A> file in the repo shows the expected format for local development ā use that for Docker Compose, and use Kubernetes Secrets for Kyma.</DIV><H3 id="toc-hId--705670575">Deploying n8n</H3><P>With storage claimed and the secret created, apply the main deployment manifest:</P><pre class="lia-code-sample language-bash"><code>kubectl apply -f n8n-kyma.yaml -n n8n</code></pre><P>The <CODE><A href="#toc-hId-14385313-h_8516816302171778598759145" target="_self" rel="nofollow noopener noreferrer">n8n-kyma.yaml</A></CODE> file defines a Kubernetes <STRONG>Deployment</STRONG> ā a declaration of the desired state for the n8n application. It tells Kubernetes to run one Pod using the official <CODE>n8nio/n8n:latest</CODE> image, mount the persistent volume at <CODE>/home/node/.n8n</CODE> (where n8n writes all its data), and inject a set of environment variables including the host domain, protocol, port, and CORS origins.</P><P>Before applying, open <A href="#toc-hId-14385313-h_8516816302171778598759145" target="_self" rel="nofollow noopener noreferrer"><CODE>n8n-kyma.yaml</CODE></A> and replace the two placeholder values:</P><DIV class="">Placeholder What to put here <TABLE><TBODY><TR><TD><CODE><your-encryption-key></CODE></TD><TD>The key you generated above ā or better, reference the Kubernetes Secret</TD></TR><TR><TD><CODE><your-kyma-domain></CODE></TD><TD>Your Kyma app domain, e.g. <CODE>n8n.abc1234.kyma.ondemand.com</CODE></TD></TR></TBODY></TABLE></DIV><P>Once applied, Kubernetes pulls the n8n image from Docker Hub and starts the container. Track the startup with:</P><pre class="lia-code-sample language-bash"><code>kubectl get pods -n n8n --watch</code></pre><P>The Pod status will move from <CODE>Pending</CODE> (image being pulled) to <CODE>ContainerCreating</CODE> to <CODE>Running</CODE>. Once it shows <CODE>Running</CODE> and <CODE>1/1</CODE> in the Ready column, n8n is up.</P><DIV class=""><DIV class=""> </DIV></DIV><HR /><!-- āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā --><H2 id="toc-hId--608781073"><SPAN class="">5. </SPAN>Creating the Service ā The Internal Bridge</H2><P>At this point n8n is running inside a Pod. But Pods in Kubernetes are ephemeral ā every time the Pod is restarted, recreated, or updated, it gets a new internal IP address. Any other component trying to reach n8n by IP would break on the next restart.</P><P>A <STRONG>Kubernetes Service</STRONG> solves this. It is a stable, named internal endpoint that always points to the correct Pod, regardless of how many times that Pod has been replaced. The Service does not change; the Pod behind it can.</P><P>Create a file called <CODE>n8n-service.yaml</CODE> and apply it to the cluster. The most important part of the Service definition is the <STRONG>selector</STRONG>:</P><pre class="lia-code-sample language-yaml"><code>selector:
app: n8n</code></pre><P>This tells the Service: <EM>"route traffic to any Pod that carries the label <CODE>app: n8n</CODE>."</EM> If you look at <A href="#toc-hId-14385313-h_8516816302171778598759145" target="_self" rel="nofollow noopener noreferrer"><CODE>n8n-kyma.yaml</CODE></A>, the Pod template carries exactly that label ā that is how the Service and the Deployment find each other. External traffic arriving at the Service on port <CODE>80</CODE> is forwarded to the n8n container on port <CODE>5678</CODE>.</P><pre class="lia-code-sample language-bash"><code>kubectl apply -f n8n-service.yaml -n n8n</code></pre><DIV class=""><DIV class=""> </DIV><DIV class=""><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="The Kyma Dashboard Services view in the n8n namespace, showing n8n-service listed with its cluster IP." style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/404380i608FB05D7F40B7FE/image-size/large?v=v2&px=999" role="button" title="shopithan28_3-1777451622345.png" alt="The Kyma Dashboard Services view in the n8n namespace, showing n8n-service listed with its cluster IP." /><span class="lia-inline-image-caption" onclick="event.preventDefault();">The Kyma Dashboard Services view in the n8n namespace, showing n8n-service listed with its cluster IP.</span></span><P> </P><P> </P></DIV></DIV><HR /><!-- āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā --><H2 id="toc-hId--805294578"><SPAN class="">6. </SPAN>The API Rule ā The External Gateway</H2><P>The Service we just created is internal. It is reachable within the cluster, but not from your browser. The final piece is exposing n8n to the outside world through <STRONG>Kyma's API Gateway</STRONG>.</P><P>Kyma uses a custom resource called an <STRONG>APIRule</STRONG> to manage external access to services. An APIRule is more than just an ingress route ā it integrates with Kyma's Istio service mesh and the Oathkeeper access control layer. This means you can add authentication policies directly at the gateway level, before a request ever reaches your application.</P><H3 id="toc-hId--1295211090">Creating the API Rule via the Kyma Dashboard</H3><P>The simplest way to create an APIRule for a first deployment is directly through the Kyma Dashboard UI:</P><OL><LI>Open the Kyma Dashboard and switch to the <CODE>n8n</CODE> namespace using the namespace selector at the top.</LI><LI>In the left-hand navigation, find <STRONG>"API Rules"</STRONG> under the Networking section.</LI><LI>Click <STRONG>"Create API Rule"</STRONG>.</LI><LI>Fill in the form:<UL><LI><STRONG>Name:</STRONG> <CODE>n8n</CODE></LI><LI><STRONG>Service:</STRONG> select <CODE>n8n-service</CODE>, port <CODE>80</CODE></LI><LI><STRONG>Host:</STRONG> choose a subdomain ā <CODE>n8n</CODE> is a natural choice. Kyma will append your cluster's base domain automatically, resulting in a URL like <CODE><A href="https://n8n.abc1234.kyma.ondemand.com" target="_blank" rel="noopener nofollow noreferrer">https://n8n.abc1234.kyma.ondemand.com</A></CODE>.</LI><LI><STRONG>Access Strategy:</STRONG> set to <CODE>NoAuth</CODE> for the initial setup.</LI></UL></LI></OL><DIV class=""><DIV class=""> </DIV></DIV><H3 id="toc-hId--1491724595">A Word on the Access Strategy</H3><P>Setting the access strategy to <CODE>NoAuth</CODE> means Kyma's gateway passes all requests through to n8n without performing any additional authentication check at the gateway level. This does <STRONG>not</STRONG> mean the instance is unsecured ā n8n has its own built-in user management and requires account login before anyone can see or edit workflows.</P><P>For a first deployment and internal team use, this is a perfectly reasonable starting point. For production environments or public-facing instances, you can tighten this by configuring the APIRule to use JWT validation against <STRONG>SAP Identity Authentication Service (IAS)</STRONG>, restricting access to authenticated SAP BTP users before requests even reach the n8n container.</P><H3 id="toc-hId--1688238100">Verify External Access</H3><P>Once the APIRule is created, copy the host URL from the dashboard and open it in your browser. You should be greeted by the n8n login screen. If the browser shows a connection error, give it 30ā60 seconds ā APIRules take a moment to propagate through the Istio gateway. A green checkmark on the APIRule in the Kyma Dashboard confirms it was processed successfully.</P><DIV class=""><DIV class=""> </DIV><DIV class=""><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Browser showing the n8n login screen at the live Kyma domain URL ā the confirmation that the full stack is working end-to-end." style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/404386iB41C06F95C418411/image-size/large?v=v2&px=999" role="button" title="Screenshot 2026-04-29 104246.png" alt="Browser showing the n8n login screen at the live Kyma domain URL ā the confirmation that the full stack is working end-to-end." /><span class="lia-inline-image-caption" onclick="event.preventDefault();">Browser showing the n8n login screen at the live Kyma domain URL ā the confirmation that the full stack is working end-to-end.</span></span></P><P> </P><P> </P></DIV></DIV><P> </P><HR /><P><!-- āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā --><!-- What's next --></P><P> </P><H5 id="toc-hId-14385313-h_3970629631241778598709429"><STRONG>docker-compose.yml : </STRONG></H5><pre class="lia-code-sample language-yaml"><code>services:
n8n:
image: n8nio/n8n:latest
restart: always
ports:
- "5678:5678"
environment:
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_HOST=localhost
- N8N_PROTOCOL=http
- NODE_ENV=production
volumes:
- ./n8n_data:/home/node/.n8n</code></pre><P> </P><P> </P><H5 id="toc-hId-14385313-h_4726520522591778598820676"><STRONG>.env.example :</STRONG></H5><pre class="lia-code-sample language-abap"><code># n8n Encryption Key ā generate with: openssl rand -hex 32
N8N_ENCRYPTION_KEY=your-random-32-byte-hex-key-here</code></pre><P> </P><H5 id="toc-hId-14385313-h_8516816302171778598759145"><STRONG>n8n-kyma.yaml</STRONG><STRONG> : </STRONG></H5><pre class="lia-code-sample language-yaml"><code>apiVersion: apps/v1
kind: Deployment
metadata:
name: n8n
namespace: n8n
spec:
replicas: 1
selector:
matchLabels:
app: n8n
template:
metadata:
labels:
app: n8n
spec:
securityContext:
runAsUser: 1000
fsGroup: 1000
containers:
- name: n8n
image: n8nio/n8n:latest
env:
- name: N8N_ENCRYPTION_KEY
value: "<your-encryption-key>"
- name: N8N_HOST
value: "<your-kyma-domain>"
- name: WEBHOOK_URL
value: "https://<your-kyma-domain>/"
- name: N8N_PROTOCOL
value: "https"
- name: N8N_PORT
value: "5678"
- name: N8N_CORS_ALLOWED_ORIGINS
value: "https://<your-kyma-domain>/"
- name: N8N_USER_FOLDER
value: "/home/node/.n8n"
volumeMounts:
- name: n8n-data
mountPath: /home/node/.n8n
volumes:
- name: n8n-data
persistentVolumeClaim:
claimName: n8n-pvc</code></pre><P> </P><H5 id="toc-hId-14385313-h_8516816302171778598759145"><STRONG>n8n-pvc.yaml: <BR /></STRONG></H5><pre class="lia-code-sample language-yaml"><code>apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: n8n-pvc
namespace: n8n
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi</code></pre><P><STRONG> </STRONG></P><P> </P><P><STRONG>AI Usage Disclosure:</STRONG><SPAN> Gen AI was used exclusively for linguistic refinements such as grammar, spelling, and phrasing as well as for the structural organisation of this text. All conceptual content, technical knowledge, architecture decisions, and implementation steps were developed independently by the author.</SPAN></P><P> </P><P> </P><DIV class=""><DIV class="">Up Next ā Part 2 of 3</DIV><H3 id="toc-hId-1792345362">Create a Custom Language Model Node for SAP AI Core</H3><P>You now have a live, persistent n8n instance running on SAP BTP. In Part 2, we take it further: building a <STRONG>custom n8n node</STRONG> that connects directly to <STRONG>SAP AI Core's Generative AI Hub</STRONG> ā turning your automation platform into an AI-capable engine that can call SAP-hosted language models like GPT-4o or Llama 3 from within any workflow. The full source code is already available; we will walk through exactly how it works and how to bundle it into your Kyma deployment. </P><P> </P><P> </P></DIV><DIV class=""><DIV class=""> </DIV></DIV><DIV class=""> </DIV>2026-05-13T11:24:38.536000+02:00https://community.sap.com/t5/technology-blog-posts-by-sap/part-2-create-a-custom-sap-ai-core-chat-model-node-in-n8n/ba-p/14390936Part 2 - Create a Custom SAP AI Core Chat Model Node in n8n2026-05-14T14:06:32.507000+02:00Shopithan_Goneswaranhttps://community.sap.com/t5/user/viewprofilepage/user-id/2176905<P> </P><DIV class=""><!-- Top bar --><DIV class=""><SPAN>SAP Community ā Technology Blogs</SPAN> <SPAN class="">n8n meets SAP ā 3-Part Series</SPAN></DIV><!-- Hero --><DIV class=""><SPAN class="">Part 2 of 3</SPAN><H1 id="toc-hId-1666851318">Bringing SAP AI Core into n8n ā Building and Deploying a Custom Chat Model Node</H1><P class="">How to build a custom node that connects n8n's AI Agent to SAP's Generative AI Hub, package it into a Docker image, and deploy it to your Kyma instance.</P></DIV><!-- Body --><DIV class=""><DIV class=""><P>Series Overview</P><OL><LI><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/n8n-on-sap-btp-kyma-part-1-from-local-docker-to-enterprise-grade-deployment/ba-p/14385313" target="_self">Enterprise Automation ā Deploying n8n on SAP BTP Kyma</A></LI><LI><SPAN><STRONG>Create a Custom Language Model Node for SAP AI Core</STRONG> <span class="lia-unicode-emoji" title=":round_pushpin:">š</span></SPAN></LI><LI><A href="https://community.sap.com/t5/technology-blog-posts-by-sap/workflow-n8n-integration-with-joule-studio/ba-p/14392294" target="_self">Workflow n8n Integration with Joule Studio</A></LI></OL></DIV><!-- Intro --><P>In Part 1 we deployed a clean, vanilla n8n instance on SAP BTP, Kyma runtime. That instance uses the official <CODE>n8nio/n8n:latest</CODE> Docker image with no modifications. It works perfectly for most integration scenarios ā but it has no built-in understanding of SAP AI Core or the Generative AI Hub.</P><P>n8n solves this through a <STRONG>custom node</STRONG> system. Any developer can write a node, package it as a Node.js module, and bake it into a custom Docker image on top of the official n8n base. That image replaces the default one in your deployment, and your new node appears in the editor alongside every built-in node. This post walks through exactly that process from understanding how the node works, to building and testing it locally, pushing it to a private registry, and finally swapping the image in your Kyma deployment. At the end, we build a first real AI workflow to put it all together.</P><P><SPAN>All files used in this post are available at the bottom of this page. You can create each file locally by copying the code provided directly from there. The custom node in this post was built on top of the official n8n-nodes-starter template provided by n8n, which gives you the correct project structure.</SPAN></P><HR /><!-- Section 1 --><H2 id="toc-hId-1599420532"><SPAN class="">1</SPAN> How the Custom Node Works</H2><P>Before touching a terminal, it helps to understand what kind of node this actually is because it is not a standard action node. It is a <STRONG>Language Model node</STRONG>.</P><H3 id="toc-hId-1531989746">Nodes in n8n's AI System</H3><P>n8n has a built-in AI framework based on LangChain. Within that framework, nodes play different roles. An <STRONG>AI Agent node</STRONG> orchestrates a conversation loop , it receives a user message, decides whether to call a tool or respond directly, and produces a final answer. But the Agent node itself does not contain a language model. It expects one to be plugged into it from the outside.</P><P>That is exactly what our custom node does. It acts as a <STRONG>model provider, </STRONG>it outputs a ready-to-use language model connection that the Agent node can pick up and use. In n8n's visual canvas, you connect our SAP AI Core node to the "Chat Model" input of an Agent node, and from that point forward the agent uses SAP AI Core as its brain.</P><P> </P><DIV class=""><DIV class=""> </DIV><DIV class=""><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="n8n canvas showing the SAP AI Core Chat Model node connected to the "Chat Model" input of an AI Agent node" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/407058i56DC828A16EBF59B/image-size/large?v=v2&px=999" role="button" title="shopithan28_0-1778107275864.png" alt="n8n canvas showing the SAP AI Core Chat Model node connected to the "Chat Model" input of an AI Agent node" /><span class="lia-inline-image-caption" onclick="event.preventDefault();">n8n canvas showing the SAP AI Core Chat Model node connected to the "Chat Model" input of an AI Agent node</span></span></DIV><DIV class=""> </DIV><DIV class=""> </DIV><H3 id="toc-hId-1335476241"> </H3><H3 id="toc-hId-1138962736"><SPAN>The Authentication Flow</SPAN></H3><P><SPAN>SAP AI Core is protected by </SPAN><STRONG>XSUAA</STRONG><SPAN> ā SAP's OAuth2 authorization server. Every request to the Generative AI Hub must carry a valid Bearer token. The node handles this automatically: when a workflow runs, the node fetches a fresh token from the XSUAA token endpoint using the Client ID and Client Secret from the saved credentials, then passes that token to every subsequent API call.</SPAN></P><P> </P></DIV><P>This token fetch uses the <STRONG>client credentials</STRONG> grant type, the standard machine-to-machine OAuth2 flow. There is no user redirect or browser interaction involved. The credentials are stored securely in n8n's encrypted credential store (encrypted with the <CODE>N8N_ENCRYPTION_KEY</CODE> from Part 1).</P><P> </P><H3 id="toc-hId-942449231">The LangChain Bridge</H3><P>SAP AI Core's Generative AI Hub exposes an API that is compatible with the OpenAI chat completions format. The custom node takes advantage of this by using LangChain's <CODE>ChatOpenAI</CODE> client and pointing it at the AI Core endpoint instead of OpenAI's servers. This gives n8n's entire AI framework immediate compatibility with any model deployed in your AI Core resource group, with no custom request logic required.</P><P>The node constructs the endpoint URL dynamically from two pieces of information you provide: the <STRONG>Base URL</STRONG> from your AI Core service key, and the <STRONG>Deployment ID</STRONG> of the specific model you want to call. It also attaches the <CODE>ai-resource-group</CODE> header to every request so AI Core knows which resource group to bill and route the request to.</P><P> </P><H3 id="toc-hId-745935726">Node Parameters</H3><P>When you add the node to a canvas, you configure it with four fields:</P><P> </P><TABLE><TBODY><TR><TD><STRONG>Parameter </STRONG></TD><TD><STRONG>Where to find it</STRONG></TD><TD><STRONG>Example value</STRONG></TD></TR><TR><TD width="132.712px" height="57px">Base URL</TD><TD width="311.337px" height="57px">AI Core service key ā field <CODE>AI_API_URL</CODE></TD><TD width="448.75px" height="57px"><CODE><A href="https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com" target="_blank" rel="noopener nofollow noreferrer">https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com</A></CODE></TD></TR><TR><TD width="132.712px" height="57px">Deployment ID</TD><TD width="311.337px" height="57px">SAP AI Launchpad ā your active deployment</TD><TD width="448.75px" height="57px"><CODE>d1a2b3c4e5f6</CODE></TD></TR><TR><TD width="132.712px" height="30px">Model Name</TD><TD width="311.337px" height="30px">The model behind your deployment</TD><TD width="448.75px" height="30px"><CODE>gpt-4o</CODE></TD></TR><TR><TD width="132.712px" height="57px">Resource Group</TD><TD width="311.337px" height="57px">AI Core resource group name</TD><TD width="448.75px" height="57px"><CODE>default</CODE></TD></TR></TBODY></TABLE><P>The credentials (Access Token URL, Client ID, Client Secret) are stored separately in n8n's credential manager and reused across multiple nodes. You create them once and reference them from any number of SAP AI Core nodes in any workflow.</P><DIV class=""><DIV class=""> </DIV><DIV class=""> </DIV><DIV class=""><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="The SAP AI Core Chat Model node configuration panel in n8n, showing all four parameters filled in and the credential selector pointing to a saved "SAP AI Core OAuth2" credential." style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/407059iD1E121ACD454A75A/image-size/large?v=v2&px=999" role="button" title="shopithan28_1-1778107545022.png" alt="The SAP AI Core Chat Model node configuration panel in n8n, showing all four parameters filled in and the credential selector pointing to a saved "SAP AI Core OAuth2" credential." /><span class="lia-inline-image-caption" onclick="event.preventDefault();">The SAP AI Core Chat Model node configuration panel in n8n, showing all four parameters filled in and the credential selector pointing to a saved "SAP AI Core OAuth2" credential.</span></span><P> </P><P> </P></DIV><P> </P></DIV><H2 id="toc-hId-420339502"><SPAN class="">2</SPAN> Building the Image Locally</H2><P>The repository contains a multi-stage <STRONG>Dockerfile</STRONG> that handles everything: compiling the TypeScript source, assembling the distributable files, and embedding them into a fresh n8n image. You do not need to understand every line of it ā but it is worth knowing what the two stages do.</P><P> </P><H3 id="toc-hId-352908716">Stage 1 ā The Builder</H3><P>The first stage uses a standard Node.js 20 image as a build environment. It installs all dependencies (including dev tools like the TypeScript compiler), copies the source files, normalises line endings to avoid cross-platform issues between Windows and Linux, and runs the TypeScript build. The output is a compiled <CODE>dist/</CODE> folder containing plain JavaScript ā ready to run in any Node.js environment.</P><H3 id="toc-hId-156395211">Stage 2 ā The Final Image</H3><P>The second stage starts fresh from <CODE>n8nio/n8n:latest</CODE> ā the same base image used in Part 1. It copies only the compiled output from Stage 1 into a specific system path: <CODE>/usr/local/lib/node_modules/n8n-nodes-sap-ai-core/</CODE>.</P><DIV class=""><DIV><STRONG>Why that path specifically?</STRONG> The PersistentVolumeClaim from Part 1 is mounted at <CODE>/home/node/.n8n</CODE>. If the node were installed there, the volume mount would hide it at runtime ā the node would silently disappear. Installing it to a system path outside the mount point ensures it is always present, regardless of what the PVC contains.</DIV></DIV><H3 id="toc-hId--115349663">Running the Build</H3><P>Clone the repository, then build the image with a name and tag of your choice:</P></DIV></DIV><P> </P><pre class="lia-code-sample language-bash"><code>docker build -t n8n-sap-ai-core:latest .</code></pre><P> </P><P>Docker will work through both stages. The first time you run this it will take a couple of minutes as it downloads base images and compiles dependencies. Subsequent builds are faster thanks to Docker's layer cache.</P><H2 id="toc-hId--18460161"> </H2><H2 id="toc-hId--214973666"><SPAN class="">3</SPAN> Testing the Image Locally</H2><P>Before pushing anything to a registry or touching the Kyma deployment, it is worth confirming that the image works as expected on your local machine. The companion repository from Part 1 already contains a <CODE>docker-compose.yml</CODE> for running n8n locally ā you just need to point it at your new custom image instead of the default one.</P><P>Open the <CODE>docker-compose.yml</CODE> and change the <CODE>image</CODE> field from <CODE>n8nio/n8n:latest</CODE> to <CODE>n8n-sap-ai-core:latest</CODE> (the tag you used in the build step). Then start the stack:</P><P> </P><pre class="lia-code-sample language-bash"><code>docker compose up -d</code></pre><P> </P><P>Open <CODE><A href="http://localhost:5678" target="_blank" rel="noopener nofollow noreferrer">http://localhost:5678</A></CODE> in your browser. Log in, create a new workflow, and open the node picker. Search for <STRONG>"SAP"</STRONG> ā the SAP AI Core Chat Model node should appear in the results. If it does, the custom image is working correctly.</P><DIV class=""><DIV> </DIV></DIV><DIV class=""><DIV class=""> </DIV><DIV class=""><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="n8n node picker (local instance) with "SAP" typed in the search field and the SAP AI Core Chat Model node visible in the results list with the SAP icon." style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/407060i7E70EA758F15BA0F/image-size/large?v=v2&px=999" role="button" title="shopithan28_2-1778107768304.png" alt="n8n node picker (local instance) with "SAP" typed in the search field and the SAP AI Core Chat Model node visible in the results list with the SAP icon." /><span class="lia-inline-image-caption" onclick="event.preventDefault();">n8n node picker (local instance) with "SAP" typed in the search field and the SAP AI Core Chat Model node visible in the results list with the SAP icon.</span></span><P> </P><P> </P></DIV></DIV><H2 id="toc-hId--411487171"><SPAN class="">4</SPAN> Pushing to a Private Registry</H2><P>Kyma pulls container images from a registry ā either Docker Hub (public) or a private registry that you control. For enterprise use, a private registry is the right choice: it keeps your custom image within your organisation's infrastructure and prevents unauthorised access.</P><P>The exact registry URL depends on what your organisation uses. Common choices include the <STRONG>SAP Container Registry</STRONG>, a registry in your cloud provider's environment or a self-hosted Harbor instance. In this Tutorial we used a Docker Hub private repository. </P><H3 id="toc-hId--901403683">Tag and Push</H3><P>First, tag your locally built image with the full registry path. Then push it:</P><P> </P><pre class="lia-code-sample language-bash"><code># Tag the image with your registry path
docker tag n8n-sap-ai-core:latest <your-registry>/n8n-sap-ai-core:1.0.0
# Push to the registry
docker push <your-registry>/n8n-sap-ai-core:1.0.0</code></pre><P> </P><P>Use a specific version tag (like <CODE>1.0.0</CODE>) rather than <CODE>latest</CODE> for anything going to Kyma. This makes rollbacks straightforward if a new version causes problems, you simply update the deployment YAML back to the previous tag and re-apply.</P><P> </P><H3 id="toc-hId--1097917188">Registry Credentials in Kyma</H3><P>If your registry is private, Kyma needs credentials to pull the image. You provide these as a Kubernetes <STRONG>imagePullSecret, </STRONG> a Secret of type <CODE>kubernetes.io/dockerconfigjson</CODE> that contains your registry login. Create the secret in the <CODE>n8n</CODE> namespace and reference it in your deployment manifest. The Kyma Dashboard has a built-in form for creating Docker registry secrets under <STRONG>Configuration ā Secrets</STRONG>.</P><HR /><P><!-- Section 5 --></P><H2 id="toc-hId--1001027686"><SPAN class="">5</SPAN> Updating the Kyma Deployment</H2><P>With the image in the registry, the final step is telling the Kyma deployment to use it. Open <CODE>n8n-kyma.yaml</CODE> from the Part 1 repository and locate the <CODE>image</CODE> field inside the container spec. Change it from the default n8n image to your custom one:</P><P> </P><pre class="lia-code-sample language-bash"><code># Before
image: n8nio/n8n:latest
# After
image: <your-registry>/n8n-sap-ai-core:1.0.0</code></pre><P> </P><P>If your registry is private, also add the <CODE>imagePullSecrets</CODE> entry pointing to the secret you created in the previous step. Then apply the updated manifest:</P><P> </P><pre class="lia-code-sample language-bash"><code>kubectl apply -f n8n-kyma.yaml -n n8n</code></pre><P> </P><P>Kubernetes detects that the image has changed and performs a rolling update ā it starts a new Pod with the new image, waits for it to become healthy, then terminates the old one. Your n8n instance will be briefly in a transitional state but will not go fully offline. Watch the rollout with:</P><P> </P><pre class="lia-code-sample language-bash"><code>kubectl rollout status deployment/n8n -n n8n</code></pre><P> </P><P>Once the rollout is complete, open your live n8n Kyma URL in a browser. Create a new workflow and search for "SAP" in the node picker. The custom node should now be available in your production instance.</P><H2 id="toc-hId--1197541191"> </H2><H2 id="toc-hId--1394054696"><SPAN class="">6</SPAN> Building the Sample Workflow</H2><P><SPAN>With the custom node deployed, we can build a real AI workflow. The scenario is an </SPAN><STRONG>IT Helpdesk Assistant, </STRONG><SPAN>a conversational agent that can look up tickets, knowledge articles, and employee skills from a live OData API and answer questions about them in natural language. This workflow also serves as the foundation for Part 3, where we surface it through Joule Studio.</SPAN></P><P> </P><H3 id="toc-hId--1883971208">Workflow Overview</H3><H3 id="toc-hId--1912301022">Step 1 ā Create the Workflow & add Agent-Node</H3><DIV class="">In n8n, create a new workflow and name it<SPAN> </SPAN><EM>IT-Helpdesk-Agent</EM>. Add an AI Agent node to a blank canvas, n8n automatically pairs it with a Chat Trigger, so both nodes appear together. This gives you a built-in chat interface directly inside n8n where you can test the agent in real time without any additional setup.</DIV><H3 id="toc-hId--2108814527">Step 2 ā Add the AI Agent Node</H3><P>Add an <STRONG>AI Agent</STRONG> node. In its configuration, set the <STRONG>System Message</STRONG> to give the agent a proper persona. </P><DIV class=""><DIV class=""> </DIV><DIV class=""> </DIV><DIV class=""><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="AI Agent node configuration panel showing the Prompt field and the "Chat Model" and "Tools" inputs visible at the bottom of the node ready to be connected." style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/407068i6283C202A7609B8E/image-size/large?v=v2&px=999" role="button" title="shopithan28_0-1778109067467.png" alt="AI Agent node configuration panel showing the Prompt field and the "Chat Model" and "Tools" inputs visible at the bottom of the node ready to be connected." /><span class="lia-inline-image-caption" onclick="event.preventDefault();">AI Agent node configuration panel showing the Prompt field and the "Chat Model" and "Tools" inputs visible at the bottom of the node ready to be connected.</span></span><P> </P><P> </P></DIV></DIV><H3 id="toc-hId-1989639264">Step 3 ā Connect the SAP AI Core Chat Model</H3><P>Add the <STRONG>SAP AI Core Chat Model</STRONG> node to the canvas. Configure it with your AI Core credentials, Base URL, Deployment ID, and the model name of your active deployment. Connect the output of this node to the <STRONG>Chat Model</STRONG> input of the AI Agent node.</P><P>The agent will now use your SAP-hosted model as its reasoning engine for every workflow execution.</P><P> </P><H3 id="toc-hId-1793125759">Step 4 ā Add the OData Tool</H3><P><SPAN>Add an </SPAN><STRONG>HTTP Request Tool</STRONG><SPAN> node, this is the tool that gives the agent access to real data. The <STRONG>Description</STRONG> tells the agent when and how to use this tool. The <STRONG>URL</STRONG> is where the dynamic behaviour lives. Instead of a fixed endpoint, the URL uses n8n's <CODE>$fromAI()</CODE> expression to let the model itself decide which entity to query:</SPAN><BR /><!-- Series nav --></P><P> </P><P><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="httpRequest node configuration panel showing the Description field and the URL field." style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/407069i3428D067B1F7A37D/image-size/large?v=v2&px=999" role="button" title="shopithan28_1-1778109453938.png" alt="httpRequest node configuration panel showing the Description field and the URL field." /><span class="lia-inline-image-caption" onclick="event.preventDefault();">httpRequest node configuration panel showing the Description field and the URL field.</span></span></P><P> </P><P><SPAN>At this point you have a working AI workflow running entirely within your SAP BTP environment, powered by SAP AI Core, querying a live OData API, and producing natural language answers. No external AI provider, no data leaving your BTP boundary.</SPAN></P><P> </P><H5 id="toc-hId-1009806240"><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="shopithan28_2-1778109802482.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/407070i33327B571F9BAC7E/image-size/large?v=v2&px=999" role="button" title="shopithan28_2-1778109802482.png" alt="shopithan28_2-1778109802482.png" /></span></H5><P> </P><HR /><H5 id="toc-hId-813292735"><SPAN>SapAiCore.node.ts:<BR /><BR /></SPAN></H5><pre class="lia-code-sample language-javascript"><code>import {
INodeType,
INodeTypeDescription,
ISupplyDataFunctions,
SupplyData,
NodeConnectionTypes,
} from 'n8n-workflow';
import { ChatOpenAI } from '@langchain/openai';
import * as https from 'https';
import { URL } from 'url';
function fetchXsuaaToken(tokenUrl: string, clientId: string, clientSecret: string): Promise<string> {
return new Promise((resolve, reject) => {
const body = 'grant_type=client_credentials';
const basicAuth = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
let url: URL;
try {
url = new URL(tokenUrl);
} catch {
return reject(new Error(`Invalid accessTokenUrl: "${tokenUrl}"`));
}
const req = https.request({
hostname: url.hostname,
path: url.pathname + (url.search ?? ''),
port: url.port ? parseInt(url.port) : 443,
method: 'POST',
headers: {
'Authorization': `Basic ${basicAuth}`,
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(body),
},
}, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
const json = JSON.parse(data);
if (json.access_token) {
resolve(json.access_token);
} else {
reject(new Error(
`XSUAA [${res.statusCode}]: ${data} | ` +
`clientId prefix: ${clientId.substring(0, 10)}... | ` +
`tokenUrl: ${tokenUrl}`
));
}
} catch {
reject(new Error(`XSUAA parse error [${res.statusCode}]: ${data}`));
}
});
});
req.on('error', (err) => reject(new Error(`XSUAA request error: ${err.message}`)));
req.write(body);
req.end();
});
}
export class SapAiCore implements INodeType {
description: INodeTypeDescription = {
displayName: 'SAP AI Core Chat Model',
name: 'sapAiCoreChatModel',
icon: 'file:sap.svg',
group: ['transform'],
version: 1,
description: 'SAP Generative AI Hub Chat Model via AI Core',
defaults: {
name: 'SAP AI Core Model',
},
inputs: [],
outputs: [NodeConnectionTypes.AiLanguageModel],
credentials: [
{
name: 'sapAiCoreAuth',
required: true,
},
],
properties: [
{
displayName: 'Base URL',
name: 'baseUrl',
type: 'string',
default: '',
required: true,
placeholder: 'https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com',
description: 'The AI_API_URL from your SAP Service Key',
},
{
displayName: 'Deployment ID',
name: 'deploymentId',
type: 'string',
required: true,
default: '',
description: 'The ID of your specific model deployment in SAP AI Core',
},
{
displayName: 'Model Name',
name: 'modelName',
type: 'string',
default: 'gpt-4o',
description: 'The name of the model being used (e.g., gpt-4o, gpt-35-turbo)',
},
{
displayName: 'Resource Group',
name: 'resourceGroup',
type: 'string',
default: 'default',
description: 'The AI Core resource group',
},
],
};
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
const credentials = await this.getCredentials('sapAiCoreAuth');
const baseUrlRaw = this.getNodeParameter('baseUrl', itemIndex, '') as string;
const baseUrl = baseUrlRaw.replace(/\/$/, '').replace(/\/v2$/, '');
const deploymentId = this.getNodeParameter('deploymentId', itemIndex, '') as string;
const modelName = this.getNodeParameter('modelName', itemIndex, 'gpt-4o') as string;
const resourceGroup = this.getNodeParameter('resourceGroup', itemIndex, 'default') as string;
const accessTokenUrl = (credentials.accessTokenUrl as string ?? '').trim();
const clientId = (credentials.clientId as string ?? '').trim();
const clientSecret = (credentials.clientSecret as string ?? '').trim();
if (!accessTokenUrl || !clientId || !clientSecret) {
throw new Error(
`SAP Auth: credentials incomplete ā ` +
`tokenUrl=${accessTokenUrl ? 'OK' : 'MISSING'}, ` +
`clientId=${clientId ? 'OK' : 'MISSING'}, ` +
`clientSecret=${clientSecret ? 'OK' : 'MISSING'}. ` +
`Delete and re-create the credential in n8n if you recently changed the node.`
);
}
const apiToken = await fetchXsuaaToken(accessTokenUrl, clientId, clientSecret);
const model = new ChatOpenAI({
openAIApiKey: apiToken,
configuration: {
baseURL: `${baseUrl}/v2/inference/deployments/${deploymentId}`,
defaultHeaders: {
'Authorization': `Bearer ${apiToken}`,
'ai-resource-group': resourceGroup,
'Content-Type': 'application/json',
},
defaultQuery: {
'api-version': 'latest',
},
},
modelName: modelName,
maxRetries: 2,
});
return { response: model };
}
}</code></pre><H5 id="toc-hId-616779230"><SPAN><BR /><BR />SapAiCoreAuth.credentials.ts : </SPAN></H5><pre class="lia-code-sample language-javascript"><code>import { ICredentialType, INodeProperties } from 'n8n-workflow';
export class SapAiCoreAuth implements ICredentialType {
name = 'sapAiCoreAuth';
displayName = 'SAP AI Core OAuth2 (Client Credentials)';
documentationUrl = 'https://help.sap.com/docs/sap-ai-core';
properties: INodeProperties[] = [
{
displayName: 'Access Token URL',
name: 'accessTokenUrl',
type: 'string',
default: '',
placeholder: 'https://tenant.authentication.sap.hana.ondemand.com/oauth/token',
required: true,
},
{
displayName: 'Client ID',
name: 'clientId',
type: 'string',
default: '',
required: true,
},
{
displayName: 'Client Secret',
name: 'clientSecret',
type: 'string',
typeOptions: { password: true },
default: '',
required: true,
},
];
}</code></pre><H5 id="toc-hId-420265725"><SPAN>Dockerfile : <BR /><BR /></SPAN></H5><pre class="lia-code-sample language-abap"><code># Stage 1: Build
FROM node:20 AS builder
WORKDIR /build
COPY package*.json tsconfig.json ./
RUN npm install --include=dev --ignore-scripts
COPY . .
# This is critical for proper credential parsing and script execution in Linux containers
RUN find /build -type f \( -name "*.js" -o -name "*.ts" -o -name "*.json" \) -exec sed -i 's/\r$//' {} \; || true
RUN npm run build
# Stage 2: Final n8n Image
FROM n8nio/n8n:latest
USER root
# 1. Use a system path instead of /home/node/.n8n
# This prevents the PVC from "hiding" your files
RUN mkdir -p /usr/local/lib/node_modules/n8n-nodes-sap-ai-core
# 2. Copy the compiled files and package.json
COPY --from=builder /build/dist /usr/local/lib/node_modules/n8n-nodes-sap-ai-core/dist
COPY --from=builder /build/package.json /usr/local/lib/node_modules/n8n-nodes-sap-ai-core/package.json
# 3. Ensure the icon is in the correct place
COPY --from=builder /build/nodes/SapAiCore/sap.svg /usr/local/lib/node_modules/n8n-nodes-sap-ai-core/dist/nodes/SapAiCore/sap.svg
# 4. Install dependencies in this new system folder
WORKDIR /usr/local/lib/node_modules/n8n-nodes-sap-ai-core
RUN npm install --omit=dev --legacy-peer-deps
# 5. Set permissions so the 'node' user can read the files
RUN chown -R node:node /usr/local/lib/node_modules/n8n-nodes-sap-ai-core
USER node
WORKDIR /home/node</code></pre><H5 id="toc-hId-223752220"><SPAN> </SPAN></H5><P> </P><H5 id="toc-hId-27238715"> <SPAN>package.json : <BR /><BR /></SPAN></H5><pre class="lia-code-sample language-json"><code>{
"name": "n8n-nodes-sap-ai-core",
"version": "1.0.0",
"main": "dist/nodes/SapAiCore/SapAiCore.node.js",
"scripts": {
"build": "tsc && npm run copy:assets",
"copy:assets": "copyfiles -u 1 \"nodes/**/*.svg\" dist/"
},
"n8n": {
"n8nNodesApiVersion": 1,
"credentials": [
"dist/credentials/SapAiCoreAuth.credentials.js"
],
"nodes": [
"dist/nodes/SapAiCore/SapAiCore.node.js"
]
},
"dependencies": {
"@langchain/core": "^1.1.41",
"@langchain/openai": "^0.0.28"
},
"peerDependencies": {
"n8n-workflow": "*"
},
"devDependencies": {
"copyfiles": "^2.4.1",
"n8n-workflow": "*",
"typescript": "5.4.3"
}
}</code></pre><DIV class=""> </DIV><P><!-- AI Disclaimer --></P><DIV class=""><P><STRONG>AI Usage Disclosure:</STRONG> <SPAN>Gen AI was used exclusively for linguistic refinements such as grammar, spelling, and phrasing as well as for the structural organisation of this text. All conceptual content, technical knowledge, architecture decisions, and implementation steps were developed independently by the author.</SPAN></P></DIV><DIV class=""> </DIV><DIV class=""> </DIV><DIV class=""><DIV class=""><DIV class="">Up Next ā Part 3 of 3</DIV><P>In Part 3 we take this workflow further. We will expose it as a callable tool inside <STRONG>Joule Studio</STRONG> ā SAP's agent development environment ā turning this n8n automation into a skill that Joule agents can invoke conversationally from within any Joule-enabled SAP application.</P></DIV><DIV class=""><DIV class=""> </DIV></DIV></DIV>2026-05-14T14:06:32.507000+02:00https://community.sap.com/t5/technology-blog-posts-by-members/no-kyma-runtime-no-problem-running-kyma-locally-with-k3d/ba-p/14396846No Kyma Runtime? No Problem ā Running Kyma Locally with k3d2026-05-15T09:14:54.960000+02:00neilaspinhttps://community.sap.com/t5/user/viewprofilepage/user-id/167493<P>If you've tried to spin up a Kyma runtime on SAP BTP trial recently, you'll have hit the same wall as the rest of us: the Kyma trial has been suspended for the foreseeable future. No ETA.</P><P>For anyone actively building skills around Kyma ā whether that's for BTP development, cert prep, or just keeping pace with where SAP is heading ā this is genuinely frustrating. You can read the docs all day, but without something to deploy to, you're not really learning anything.</P><P>So here's what I did instead: I ran Kyma locally using <STRONG>k3d</STRONG> (k3s in Docker) on my MacBook. It's not a perfect substitute for BTP Kyma runtime, but it's close enough to work through real scenarios, debug real problems, and actually understand how the stack fits together.</P><P>This post covers what it takes to get there ā including the parts the official docs glide past.</P><HR /><H2 id="toc-hId-1796111853">What You Need</H2><UL><LI>Docker (running)</LI><LI><A href="https://k3d.io/" target="_blank" rel="noopener nofollow noreferrer">k3d</A> v5.x or higher</LI><LI><CODE>kubectl</CODE></LI><LI><A href="https://kyma-project.io/docs/kyma/latest/04-operation-guides/operations/01-install-kyma-cli/" target="_blank" rel="noopener nofollow noreferrer">Kyma CLI</A></LI></UL><HR /><H2 id="toc-hId-1599598348">Step 1 ā Spin Up the Cluster</H2><P>The k3d cluster creation command is doing several things at once, so it's worth understanding before you paste and run:</P><PRE><CODE>k3d cluster create kyma \
--kubeconfig-switch-context \
-p '30080:80@loadbalancer' \
-p '30443:443@loadbalancer' \
--k3s-arg '--disable=traefik@server:*' \
--k3s-arg '--tls-san=host.docker.internal@server:*'</CODE></PRE><UL><LI>The port mappings (<CODE>30080</CODE> ā <CODE>80</CODE>, <CODE>30443</CODE> ā <CODE>443</CODE>) expose the ingress gateway on localhost ports that don't require elevated privileges</LI><LI>Traefik is disabled because Kyma uses Istio as its ingress ā you don't want them fighting over traffic</LI><LI>The <CODE>tls-san</CODE> flag adds <CODE>host.docker.internal</CODE> as a valid TLS hostname, which matters later</LI></UL><P>Then create the required namespace:</P><PRE><CODE>kubectl create ns kyma-system</CODE></PRE><HR /><H2 id="toc-hId-1403084843">Step 2 ā Install the Modules</H2><P>Kyma is modular. You don't have to install everything ā pick what you need. For a useful local setup, I'd recommend starting with at minimum <STRONG>Istio</STRONG>, <STRONG>API Gateway</STRONG>, and <STRONG>Serverless</STRONG>.</P><P><STRONG>Istio</STRONG> (the service mesh ā everything else depends on this):</P><PRE><CODE>kubectl label namespace kyma-system istio-injection=enabled --overwrite
kubectl apply -f https://github.com/kyma-project/istio/releases/latest/download/istio-manager.yaml
kubectl apply -f https://github.com/kyma-project/istio/releases/latest/download/istio-default-cr.yaml</CODE></PRE><P><STRONG>Serverless</STRONG> (for Functions):</P><PRE><CODE>kubectl apply -f https://github.com/kyma-project/serverless-manager/releases/latest/download/serverless-operator.yaml
kubectl apply -f https://github.com/kyma-project/serverless-manager/releases/latest/download/default-serverless-cr.yaml -n kyma-system</CODE></PRE><P><STRONG>API Gateway</STRONG> (exposes your functions externally via APIRules):</P><PRE><CODE>kubectl label namespace kyma-system istio-injection=enabled --overwrite
kubectl apply -f https://github.com/kyma-project/api-gateway/releases/latest/download/api-gateway-manager.yaml
kubectl apply -f https://github.com/kyma-project/api-gateway/releases/latest/download/apigateway-default-cr.yaml</CODE></PRE><P>Give everything a couple of minutes to come up. You can watch progress with:</P><PRE><CODE>kubectl get pods -n kyma-system --watch</CODE></PRE><HR /><H2 id="toc-hId-1206571338">Step 3 ā Fix CoreDNS </H2><P>This is the step that will silently break everything if you skip it.</P><P>By default, <CODE>local.kyma.dev</CODE> subdomains don't resolve to anything useful inside the cluster. You need to tell CoreDNS to rewrite them to point at the Istio ingress gateway:</P><PRE><CODE>cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns-custom
namespace: kube-system
data:
kyma.override: |
rewrite name regex (.*)\.local\.kyma\.dev istio-ingressgateway.istio-system.svc.cluster.local
EOF
kubectl rollout restart deployment -n kube-system coredns</CODE></PRE><P>Without this, you'll spend a while wondering why curl is timing out and nslookup is returning nothing ā which brings me to the next section.</P><HR /><H2 id="toc-hId-1010057833">Step 4 ā Deploy a Function and Test It</H2><P>Once everything is running, deploy a simple test function. Here's a minimal Node.js example:</P><PRE><CODE>apiVersion: serverless.kyma-project.io/v1alpha2
kind: Function
metadata:
name: hello-kyma
namespace: default
spec:
runtime: nodejs20
source:
inline:
source: |
module.exports = { main: function (event, context) {
return "Hello from local Kyma!";
}}</CODE></PRE><P>Apply it, wait for it to reach <CODE>Running</CODE> state, then create an APIRule to expose it. Once that's in place, test with:</P><PRE><CODE>curl http://hello-kyma.local.kyma.dev:30080</CODE></PRE><P>Note the port ā because of the k3d port mapping, HTTP traffic goes through <CODE>30080</CODE>, not <CODE>80</CODE> directly.</P><HR /><H2 id="toc-hId-813544328">Issues</H2><P>The official quick-start docs are accurate, but they assume a fairly smooth path. Here's what debugging actually looked like once I had everything deployed but couldn't get a response through the gateway.</P><P>The problem wasn't Kyma. It was a combination of:</P><OL><LI><P><STRONG>DNS not resolving</STRONG> ā <CODE>nslookup hello-kyma.local.kyma.dev</CODE> was returning nothing. Root cause: CoreDNS rewrite not applied (see Step 3).</P></LI><LI><P><STRONG>Port confusion</STRONG> ā I'd been curling port <CODE>80</CODE> and <CODE>443</CODE> directly, which don't work from the host. The k3d port mappings mean you need <CODE>30080</CODE> for HTTP and <CODE>30443</CODE> for HTTPS from outside the cluster.</P></LI><LI><P><STRONG>Istio mTLS</STRONG> ā Once DNS was working, I hit <CODE>RBAC: access denied</CODE>. This turned out to be Istio's strict mTLS mode combined with an AuthorizationPolicy that wasn't correctly matching the ingress gateway's service account. Checking <CODE>kubectl get authorizationpolicy -n default -o yaml</CODE> and cross-referencing the actual service account name on the ingress gateway pod sorted it.</P></LI></OL><P>The fix that confirmed everything was working:</P><PRE><CODE>curl http://hello-kyma.local.kyma.dev:30080</CODE></PRE><P>Response: <CODE>Hello from local Kyma!</CODE></P><HR /><H2 id="toc-hId-617030823">A Note on HTTPS</H2><P>HTTP works cleanly with the setup above. HTTPS on a local k3d cluster involves self-signed certificates and some extra trust configuration on the host side ā that's a separate infrastructure problem rather than a Kyma one, and deserves its own post.</P><HR /><H2 id="toc-hId-420517318">Why Bother?</H2><P>Running Kyma locally isn't a replacement for BTP Kyma runtime ā you won't be able to bind real BTP services without the BTP Operator pointing at a real subaccount. But for understanding how Functions, API Gateway, Istio, and service mesh concepts actually hang together, it's genuinely valuable. You can break things, fix things, and read actual logs ā which is where the learning happens.</P><P>If SAP is going to keep Kyma trial offline for a while, this is the next best thing.</P><HR /><P><EM>Tested on macOS (Apple Silicon) with k3d v5.7, Kyma Istio module latest, and API Gateway module latest.</EM></P>2026-05-15T09:14:54.960000+02:00https://community.sap.com/t5/technology-blog-posts-by-members/running-a-python-microservice-on-local-kyma-k3d/ba-p/14397667Running a Python Microservice on Local Kyma (k3d).2026-05-17T10:46:54.455000+02:00neilaspinhttps://community.sap.com/t5/user/viewprofilepage/user-id/167493<P>This is a follow-up to my previous post on deploying Python to SAP BTP Kyma runtime. That post assumed you had a working Kyma runtime available on BTP. This one doesn't ā because if you're reading this, there's a reasonable chance you don't.</P><P>SAP has withdrawn the Kyma runtime from BTP trial accounts. If you want to learn Kyma without an enterprise subscription, you now need to provide your own environment. The answer is to run Kyma locally using k3d ā and that's what this post covers.</P><P>It also covers the bits that went wrong. There were quite a few.</P><HR /><H2 id="toc-hId-1796139785">Why Local?</H2><P>If your BTP Kyma runtime is gone, it's because SAP has withdrawn it from trial. There's no workaround on the BTP side ā the managed runtime simply isn't available anymore without an enterprise account.</P><P>Running Kyma locally on k3d ā a lightweight Kubernetes in Docker tool ā gives you a real cluster on your local machine with the same Kyma components: Istio, Serverless, API Gateway. You own the environment. It doesn't disappear on you, you can break it and fix it, and you don't need a BTP account to do anything.</P><P>The trade-off is setup complexity. There's more to configure than a managed runtime, and the local setup isn't particularly well documented. This post is the documentation I wished existed.</P><HR /><H2 id="toc-hId-1599626280">Prerequisites</H2><UL><LI>Docker Desktop (or OrbStack on macOS Apple Silicon)</LI><LI>k3d ā <CODE>brew install k3d</CODE> on macOS, or see <A href="https://k3d.io/" target="_blank" rel="noopener nofollow noreferrer">k3d.io</A> for Linux/Windows install options</LI><LI>kubectl</LI><LI>Kyma CLI (optional but useful)</LI><LI>Python 3.11+</LI></UL><HR /><H2 id="toc-hId-1403112775">Step 1 ā Create the k3d Cluster</H2><PRE><CODE>k3d cluster create kyma \
--kubeconfig-switch-context \
-p '30080:80@loadbalancer' \
-p '30443:443@loadbalancer' \
--k3s-arg '--disable=traefik@server:*' \
--k3s-arg '--tls-san=host.docker.internal@server:*'</CODE></PRE><P>A few things worth understanding here:</P><UL><LI><CODE>30080</CODE> and <CODE>30443</CODE> are the ports exposed on your local machine. Kubernetes traffic goes in through these.</LI><LI><CODE>--disable=traefik</CODE> removes the default k3d ingress controller ā Kyma uses Istio instead, and having both causes conflicts.</LI><LI><CODE>--tls-san=host.docker.internal</CODE> is needed so you can reach the API server from inside Docker containers.</LI></UL><P>Then create the Kyma system namespace:</P><PRE><CODE>kubectl create ns kyma-system</CODE></PRE><HR /><H2 id="toc-hId-1206599270">Step 2 ā Install Kyma Modules</H2><P>Follow the <A href="https://kyma-project.io/" target="_blank" rel="noopener nofollow noreferrer">Kyma quick install guide</A> to deploy the modules. At minimum you want:</P><UL><LI><STRONG>Istio</STRONG> ā service mesh and ingress gateway</LI><LI><STRONG>Serverless</STRONG> ā function runtime (optional for this post, but useful)</LI><LI><STRONG>API Gateway</STRONG> ā the APIRule controller</LI></UL><P>You can add BTP Manager if you want to connect BTP services later, but it's not needed for a basic Python deployment.</P><P>After installation:</P><PRE><CODE>kubectl get pods -n kyma-system</CODE></PRE><P>You should see the controller pods running ā <CODE>istio-controller-manager</CODE>, <CODE>api-gateway-controller-manager</CODE>, and so on. These are the operators that manage the actual Kyma components. The Istio data plane (istiod, ingress gateway) lives in <CODE>istio-system</CODE> and gets created separately when you apply the Istio custom resource.</P><HR /><H2 id="toc-hId-1010085765">Step 3 ā CoreDNS</H2><P>This step routes <CODE>*.local.kyma.dev</CODE> hostnames to the Istio ingress gateway inside the cluster. Without it, DNS resolution for your app hostnames won't work within the cluster.</P><P>Create <CODE>coredns-custom.yaml</CODE>:</P><PRE><CODE>apiVersion: v1
kind: ConfigMap
metadata:
name: coredns-custom
namespace: kube-system
data:
kyma.override: |
rewrite name regex (.*)\.local\.kyma\.dev istio-ingressgateway.istio-system.svc.cluster.local</CODE></PRE><P>Apply it and restart CoreDNS:</P><PRE><CODE>kubectl apply -f coredns-custom.yaml
kubectl rollout restart deployment -n kube-system coredns</CODE></PRE><P>Note this only affects DNS resolution <EM>inside</EM> the cluster. For curl from your local machine you'll need <CODE>/etc/hosts</CODE> entries and the <CODE>--resolve</CODE> flag ā more on that later.</P><HR /><H2 id="toc-hId-813572260">Step 4 ā Sort Out HTTPS (The Painful Bit)</H2><P>This is where I lost the most time.</P><P>The Kyma gateway is configured with <CODE>PASSTHROUGH</CODE> TLS mode by default ā it expects TLS to be terminated at the application, not the gateway. For a local setup with self-signed certificates, you want <CODE>SIMPLE</CODE> mode instead, where the gateway handles TLS termination.</P><P>Getting there required three things.</P><P><STRONG>Generate a self-signed wildcard certificate:</STRONG></P><PRE><CODE>openssl req -x509 -newkey rsa:4096 \
-keyout /tmp/kyma-tls.key \
-out /tmp/kyma-tls.crt \
-days 365 -nodes \
-subj "/CN=*.local.kyma.dev" \
-addext "subjectAltName=DNS:*.local.kyma.dev"</CODE></PRE><P><STRONG>Create a TLS secret in <CODE>istio-system</CODE>:</STRONG></P><PRE><CODE>kubectl create secret tls kyma-tls -n istio-system \
--cert=/tmp/kyma-tls.crt \
--key=/tmp/kyma-tls.key</CODE></PRE><P>The namespace matters here. The secret must be in <CODE>istio-system</CODE> because that's where the ingress gateway pod runs. Putting it anywhere else and it won't be found.</P><P><STRONG>Patch the gateway to use SIMPLE TLS:</STRONG></P><PRE><CODE>kubectl patch gateway kyma-gateway -n kyma-system --type=json \
-p='[{"op":"replace","path":"/spec/servers/0/tls","value":{"mode":"SIMPLE","credentialName":"kyma-tls"}}]'</CODE></PRE><P>Once this is done, HTTPS requests go to the gateway, TLS is terminated there, and traffic is forwarded in plain HTTP to the service.</P><P>To test from your local machine you need the <CODE>--resolve</CODE> flag in curl. This is because the TLS handshake needs to use the correct hostname as the SNI value ā if you curl <CODE>127.0.0.1</CODE> directly, the SNI won't match the certificate and the handshake fails.</P><PRE><CODE>curl -k --resolve hello-kyma.local.kyma.dev:30443:127.0.0.1 \
-H "Host: hello-kyma.local.kyma.dev" \
https://hello-kyma.local.kyma.dev:30443</CODE></PRE><P>This took longer to figure out than it should have. The SSL error (<CODE>SSL_ERROR_SYSCALL</CODE>) wasn't particularly informative.</P><HR /><H2 id="toc-hId-617058755">Step 5 ā The Flask App</H2><P>Nothing clever here. Create a project folder and the following files.</P><P><CODE>app.py</CODE>:</P><PRE><CODE>from flask import Flask, jsonify
import os
app = Flask(__name__)
@app.route("/health", methods=["GET"])
def health():
return jsonify({"status": "ok"}), 200
@app.route("/api/data", methods=["GET"])
def get_data():
return jsonify({
"message": "Hello from Kyma!",
"environment": os.getenv("ENVIRONMENT", "dev")
}), 200
if __name__ == "__main__":
port = int(os.getenv("PORT", 8080))
app.run(host="0.0.0.0", port=port)</CODE></PRE><P><CODE>requirements.txt</CODE>:</P><PRE><CODE>flask==3.0.3</CODE></PRE><P><CODE>Dockerfile</CODE>:</P><PRE><CODE>FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 8080
ENV FLASK_APP=app.py
CMD ["flask", "run", "--host=0.0.0.0", "--port=8080"]</CODE></PRE><P>Build it:</P><PRE><CODE>docker build -t btp-python-app:v1 .</CODE></PRE><P>Test it locally before touching Kubernetes:</P><PRE><CODE>docker run --rm -p 8080:8080 btp-python-app:v1
curl http://localhost:8080/health</CODE></PRE><P>If that returns <CODE>{"status":"ok"}</CODE> the app is fine. Stop it and move on.</P><HR /><H2 id="toc-hId-420545250">Step 6 ā Import the Image into k3d</H2><P>On a managed Kyma runtime you'd push the image to Docker Hub or a container registry. Locally you can skip that entirely and import directly into the cluster nodes:</P><PRE><CODE>k3d image import btp-python-app:v1 -c kyma</CODE></PRE><P>This is much faster for local development. The important thing to remember is that the image is imported into the cluster at that point in time ā if you rebuild the image you need to import again.</P><HR /><H2 id="toc-hId-224031745">Step 7 ā Kubernetes Manifests</H2><P>Create a <CODE>k8s/</CODE> folder in your project directory and add three files.</P><P><CODE>k8s/deployment.yaml</CODE>:</P><PRE><CODE>apiVersion: apps/v1
kind: Deployment
metadata:
name: btp-python-app
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: btp-python-app
template:
metadata:
labels:
app: btp-python-app
spec:
containers:
- name: btp-python-app
image: btp-python-app:v1
imagePullPolicy: Never
ports:
- containerPort: 8080
env:
- name: ENVIRONMENT
value: "kyma-local"
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"</CODE></PRE><P><CODE>imagePullPolicy: Never</CODE> is critical here. Without it Kubernetes tries to pull from a registry, fails, and the pod never starts.</P><P><CODE>k8s/service.yaml</CODE>:</P><PRE><CODE>apiVersion: v1
kind: Service
metadata:
name: btp-python-app
namespace: default
spec:
selector:
app: btp-python-app
ports:
- port: 80
targetPort: 8080</CODE></PRE><P><CODE>k8s/apirule.yaml</CODE>:</P><PRE><CODE>apiVersion: gateway.kyma-project.io/v2
kind: APIRule
metadata:
name: btp-python-app
namespace: default
spec:
gateway: kyma-system/kyma-gateway
hosts:
- btp-python-app.local.kyma.dev
service:
name: btp-python-app
port: 80
rules:
- path: /*
methods: ["GET", "POST"]
noAuth: true</CODE></PRE><P>A note on APIRule v2 syntax ā if you've seen older Kyma examples using <CODE>gateway.kyma-project.io/v1beta1</CODE>, that's deprecated as of mid-2025. The v2 differences that catch people out:</P><UL><LI><CODE>host</CODE> is now <CODE>hosts</CODE> (a list)</LI><LI>Path syntax changed ā <CODE>/.*</CODE> is no longer valid, use <CODE>/*</CODE></LI><LI><CODE>accessStrategies</CODE> with <CODE>handler: noop</CODE> is replaced by <CODE>noAuth: true</CODE></LI></UL><HR /><H2 id="toc-hId-27518240">Step 8 ā Enable Istio Sidecar Injection</H2><P>Before deploying, label the default namespace for sidecar injection:</P><PRE><CODE>kubectl label namespace default istio-injection=enabled</CODE></PRE><P>If you skip this, the APIRule will sit in an Error state with the message: <CODE>Pod does not have an injected istio sidecar</CODE>. The sidecar is required for Istio's traffic routing to work.</P><HR /><H2 id="toc-hId-178259092">Step 9 ā Deploy</H2><PRE><CODE>kubectl apply -f k8s/</CODE></PRE><P>Watch the pod:</P><PRE><CODE>kubectl get pods -l app=btp-python-app -w</CODE></PRE><P>Wait for <CODE>2/2</CODE> in the READY column ā that's your container plus the Istio sidecar. Then check the APIRule:</P><PRE><CODE>kubectl get apirule btp-python-app</CODE></PRE><P>You want <CODE>Ready</CODE>. If it shows <CODE>Error</CODE>, describe it to see why:</P><PRE><CODE>kubectl describe apirule btp-python-app</CODE></PRE><HR /><H2 id="toc-hId--18254413">Step 10 ā Test It</H2><P>Add the host entry:</P><PRE><CODE>echo "127.0.0.1 btp-python-app.local.kyma.dev" | sudo tee -a /etc/hosts</CODE></PRE><P>HTTP:</P><PRE><CODE>curl -H "Host: btp-python-app.local.kyma.dev" http://127.0.0.1:30080/health
curl -H "Host: btp-python-app.local.kyma.dev" http://127.0.0.1:30080/api/data</CODE></PRE><P>HTTPS:</P><PRE><CODE>curl -k --resolve btp-python-app.local.kyma.dev:30443:127.0.0.1 \
-H "Host: btp-python-app.local.kyma.dev" \
https://btp-python-app.local.kyma.dev:30443/health
curl -k --resolve btp-python-app.local.kyma.dev:30443:127.0.0.1 \
-H "Host: btp-python-app.local.kyma.dev" \
https://btp-python-app.local.kyma.dev:30443/api/data</CODE></PRE><P>Expected responses:</P><PRE><CODE>{"status": "ok"}
{"environment": "kyma-local", "message": "Hello from Kyma!"}</CODE></PRE><HR /><H2 id="toc-hId--214767918">Things That Tripped Me Up</H2><P><STRONG>The nodePort mismatch.</STRONG> When Istio installs it grabs a random nodePort for 443 ā in my case 31985, not 30443. k3d only exposes the ports you mapped at cluster creation, so 31985 was unreachable. Fix is to patch the Istio ingress gateway service to use the correct nodePort:</P><PRE><CODE>kubectl patch svc istio-ingressgateway -n istio-system --type='json' \
-p='[{"op": "replace", "path": "/spec/ports/2/nodePort", "value": 30443}]'</CODE></PRE><P><STRONG>The gateway didn't exist.</STRONG> The Kyma quick install guide doesn't create an Istio Gateway resource ā it installs the controller that manages them, but the actual Gateway has to be created separately. The APIRule error <CODE>Could not get specified Gateway</CODE> is the clue.</P><P><STRONG>SSL_ERROR_SYSCALL.</STRONG> This is what you get when the TLS handshake fails before it even gets to the certificate. It's not an obvious error. In this case the root cause was the gateway being in PASSTHROUGH mode with no certificate configured. Switching to SIMPLE mode and providing a self-signed cert fixed it.</P><P><STRONG>The sidecar.</STRONG> Easy to forget, easy to diagnose ā the APIRule error message is clear enough. Label the namespace before you deploy, not after.</P><HR /><H2 id="toc-hId--411281423">Debugging Toolkit</H2><P>If the app is running but the routing isn't working, test inside the pod first</P>2026-05-17T10:46:54.455000+02:00https://community.sap.com/t5/technology-blog-posts-by-members/running-a-node-js-microservice-on-local-kyma-with-btp-service-operator/ba-p/14397680Running a Node.js Microservice on Local Kyma with BTP Service Operator2026-05-17T15:31:43.631000+02:00neilaspinhttps://community.sap.com/t5/user/viewprofilepage/user-id/167493<P>This post picks up where the Python microservice post left off. If you haven't read that one, the short version is: SAP has withdrawn Kyma runtime from BTP trial, so we built a local Kyma environment on k3d instead. That gave us a working cluster with Istio, API Gateway, and a deployed Python Flask app accessible over HTTP and HTTPS.</P><P>This post does two things. First, it connects that local cluster to SAP BTP trial using the BTP Service Operator ā so you can provision real BTP services from your local environment. Second, it deploys a Node.js microservice through the same stack, partly to show the pattern works across runtimes, and partly because doing it twice cements how the pieces fit together.</P><HR /><H2 id="toc-hId-1796139840">Part 1 ā Connecting to SAP BTP Trial</H2><H3 id="toc-hId-1728709054">Why Bother?</H3><P>The local Kyma cluster works fine in isolation, but the point of Kyma in the SAP context is the integration with BTP services ā Destination Service, XSUAA, Connectivity, and so on. The BTP Service Operator is what bridges the gap. It lets you create <CODE>ServiceInstance</CODE> and <CODE>ServiceBinding</CODE> resources on your local cluster that provision real services on BTP, the same way a managed Kyma runtime would.</P><P>Your BTP trial account still works ā it's just the managed Kyma runtime that's been withdrawn. The service marketplace is still there.</P><H3 id="toc-hId-1532195549">Prerequisites</H3><P>You'll need:</P><UL><LI>The local Kyma cluster from the previous post (k3d, Istio, API Gateway, BTP Manager installed)</LI><LI>BTP CLI (<CODE>btp</CODE>) installed and logged in</LI><LI>Helm ā <CODE>brew install helm</CODE> on macOS, or see helm.sh for other platforms</LI><LI><CODE>kubectl</CODE> pointed at your local cluster</LI></UL><H3 id="toc-hId-1335682044">Step 1 ā Create a Service Manager Instance</H3><P>Set your BTP CLI target to your subaccount:</P><PRE><CODE>btp target --subaccount <your-subaccount-id></CODE></PRE><P>Then create a Service Manager instance with the <CODE>service-operator-access</CODE> plan:</P><PRE><CODE>btp create services/instance \
--subaccount <your-subaccount-id> \
--offering-name service-manager \
--plan-name service-operator-access \
--name btp-operator-access</CODE></PRE><P>This is what gives the BTP Service Operator permission to talk to the BTP service marketplace on your behalf.</P><H3 id="toc-hId-1139168539">Step 2 ā Create a Service Binding and Extract Credentials</H3><P>Create a binding for the instance:</P><PRE><CODE>btp create services/binding \
--subaccount <your-subaccount-id> \
--instance-name btp-operator-access \
--name btp-operator-key</CODE></PRE><P>Get the binding ID:</P><PRE><CODE>btp get services/binding \
--subaccount <your-subaccount-id> \
--name btp-operator-key</CODE></PRE><P>Extract the credentials:</P><PRE><CODE>btp get services/binding --id <binding-id></CODE></PRE><P>You're looking for four values: <CODE>clientid</CODE>, <CODE>clientsecret</CODE>, <CODE>url</CODE>, and <CODE>tokenurl</CODE>. You'll need these in the next step.</P><H3 id="toc-hId-942655034">Step 3 ā Install cert-manager</H3><P>The BTP Service Operator depends on cert-manager for webhook certificate management. Install it:</P><PRE><CODE>kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
kubectl wait --for=condition=Available deployment --all -n cert-manager --timeout=90s</CODE></PRE><H3 id="toc-hId-746141529">Step 4 ā Install the BTP Service Operator via Helm</H3><P>Add the SAP BTP operator Helm repo:</P><PRE><CODE>helm repo add sap-btp-operator https://sap.github.io/sap-btp-service-operator
helm repo update</CODE></PRE><P>Install the operator, passing credentials directly as Helm values:</P><PRE><CODE>helm install sap-btp-operator sap-btp-operator/sap-btp-operator \
--namespace operators \
--set manager.secret.clientid="<clientid>" \
--set manager.secret.clientsecret="<clientsecret>" \
--set manager.secret.sm_url="<url>" \
--set manager.secret.tokenurl="<tokenurl>"</CODE></PRE><P>A note on passing credentials directly via <CODE>--set</CODE> rather than creating the secret first: the alternative approach (running the setup script and then installing Helm) can cause field manager conflicts where Helm and kubectl both think they own the secret. Passing them as Helm values at install time avoids that.</P><P>Verify it's running:</P><PRE><CODE>kubectl get pods -n operators</CODE></PRE><P>You're looking for the BTP operator pod in a Running state.</P><HR /><H2 id="toc-hId-420545305">Part 2 ā The Node.js Microservice</H2><H3 id="toc-hId-353114519">Why Node.js?</H3><P>The Python post covered the core pattern ā Dockerfile, Deployment, Service, APIRule. This post uses Node.js to show the same approach works across runtimes. The Kubernetes side is identical; only the application code and Dockerfile change.</P><H3 id="toc-hId-156601014">The Application</H3><P><STRONG>app.js</STRONG></P><PRE><CODE>const express = require('express');
const app = express();
const port = process.env.PORT || 8080;
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
app.get('/api/data', (req, res) => {
res.json({
message: 'Hello from Kyma!',
environment: process.env.ENVIRONMENT || 'dev'
});
});
app.listen(port, '0.0.0.0', () => {
console.log(`Server running on port ${port}`);
});</CODE></PRE><P>Initialise the project and install dependencies:</P><PRE><CODE>mkdir -p ~/Documents/GitHub/kubernetes-course/kyma/btp-node-app
cd ~/Documents/GitHub/kubernetes-course/kyma/btp-node-app
npm init -y
npm install express</CODE></PRE><H3 id="toc-hId--115143860">The Dockerfile</H3><PRE><CODE>FROM node:20-slim
WORKDIR /app
COPY package*.json .
RUN npm install --production
COPY app.js .
EXPOSE 8080
CMD ["node", "app.js"]</CODE></PRE><H3 id="toc-hId--311657365">Build and Test Locally</H3><PRE><CODE>docker build --no-cache -t btp-node-app:v1 .</CODE></PRE><P>The <CODE>--no-cache</CODE> flag matters here. If you've run <CODE>npm install</CODE> in the project folder before adding express to <CODE>package.json</CODE>, Docker may have a cached layer from that earlier run. When it rebuilds, it sees the <CODE>npm install</CODE> step hasn't changed and uses the cache ā which means express never gets installed in the image. The <CODE>--no-cache</CODE> flag forces every layer to rebuild from scratch.</P><P>This is the kind of thing that produces a perfectly healthy-looking container that crashes immediately on startup with a module not found error. Worth knowing.</P><P>Test it before touching Kubernetes:</P><PRE><CODE>docker run --rm -p 8081:8080 btp-node-app:v1
curl http://localhost:8081/health
curl http://localhost:8081/api/data</CODE></PRE><P>If both return JSON, the app is fine. Stop the container.</P><H3 id="toc-hId--508170870">Import into k3d</H3><PRE><CODE>k3d image import btp-node-app:v1 -c kyma</CODE></PRE><H3 id="toc-hId--704684375">Kubernetes Manifests</H3><P>Create a <CODE>k8s/</CODE> folder in the project directory.</P><P><STRONG>k8s/deployment.yaml</STRONG></P><PRE><CODE>apiVersion: apps/v1
kind: Deployment
metadata:
name: btp-node-app
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: btp-node-app
template:
metadata:
labels:
app: btp-node-app
spec:
containers:
- name: btp-node-app
image: btp-node-app:v1
imagePullPolicy: Never
ports:
- containerPort: 8080
env:
- name: ENVIRONMENT
value: "kyma-local"
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"</CODE></PRE><P><STRONG>k8s/service.yaml</STRONG></P><PRE><CODE>apiVersion: v1
kind: Service
metadata:
name: btp-node-app
namespace: default
spec:
selector:
app: btp-node-app
ports:
- port: 80
targetPort: 8080</CODE></PRE><P><STRONG>k8s/apirule.yaml</STRONG></P><PRE><CODE>apiVersion: gateway.kyma-project.io/v2
kind: APIRule
metadata:
name: btp-node-app
namespace: default
spec:
gateway: kyma-system/kyma-gateway
hosts:
- btp-node-app.local.kyma.dev
service:
name: btp-node-app
port: 80
rules:
- path: /*
methods: ["GET", "POST"]
noAuth: true</CODE></PRE><H3 id="toc-hId--901197880">Deploy</H3><PRE><CODE>kubectl apply -f k8s/</CODE></PRE><P>Add the hosts entry:</P><PRE><CODE>echo "127.0.0.1 btp-node-app.local.kyma.dev" | sudo tee -a /etc/hosts</CODE></PRE><P>Check the pod and APIRule:</P><PRE><CODE>kubectl get pods -l app=btp-node-app
kubectl get apirule btp-node-app</CODE></PRE><P>Pod should be 2/2 (container plus Istio sidecar). APIRule should be Ready.</P><H3 id="toc-hId--1097711385">Test</H3><P>HTTP:</P><PRE><CODE>curl -H "Host: btp-node-app.local.kyma.dev" http://127.0.0.1:30080/health
curl -H "Host: btp-node-app.local.kyma.dev" http://127.0.0.1:30080/api/data</CODE></PRE><P>HTTPS:</P><PRE><CODE>curl -k --resolve btp-node-app.local.kyma.dev:30443:127.0.0.1 \
-H "Host: btp-node-app.local.kyma.dev" \
https://btp-node-app.local.kyma.dev:30443/health
curl -k --resolve btp-node-app.local.kyma.dev:30443:127.0.0.1 \
-H "Host: btp-node-app.local.kyma.dev" \
https://btp-node-app.local.kyma.dev:30443/api/data</CODE></PRE><P>Expected:</P><PRE><CODE>{"status": "ok"}
{"environment": "kyma-local", "message": "Hello from Kyma!"}</CODE></PRE><P>At this point you have a local Kyma cluster that:</P><UL><LI>Runs containerised microservices in multiple runtimes</LI><LI>Exposes them externally via Istio and APIRule v2</LI><LI>Is connected to SAP BTP trial via the BTP Service Operator</LI></UL><P>The next logical step is creating a <CODE>ServiceInstance</CODE> and <CODE>ServiceBinding</CODE> on the local cluster to provision a real BTP service ā Destination Service is the obvious one ā and consuming it from a microservice. That's where the SAP-specific integration story starts to get interesting.</P><P>That'll be the next post.</P>2026-05-17T15:31:43.631000+02:00https://community.sap.com/t5/technology-blog-posts-by-sap/from-concept-to-action-hands-on-with-the-cap-operator-and-the-partner/ba-p/14399663From Concept to action: Hands-On with the CAP Operator and the Partner Reference Application2026-05-21T15:34:01.526000+02:00ChristianWeisshttps://community.sap.com/t5/user/viewprofilepage/user-id/136917<H2 id="toc-hId-1796199363">Introduction</H2><P>In my recent blog series, <A class="" href="https://community.sap.com/t5/technology-blog-posts-by-sap/kyma-evolution-transforming-sap-kyma-into-a-tailor-made-saas-platform-for/ba-p/14317418" target="_blank">Kyma Evolution: Transforming SAP Kyma into a Tailor-Made SaaS Platform for CAP</A>, I explored the architecture behind turning SAP BTP, Kyma runtime into a native, clean-core-compliant foundation for multitenant Software-as-a-Service (SaaS) applications. We discussed the concept of shifting heavy operational workloadsālike tenant provisioning, container routing, and database migrationāaway from custom application logic and directly into the Kubernetes control plane via the <STRONG>CAP Operator</STRONG>.</P><P>Concepts and architecture diagrams are crucial, but as developers, we truly understand a framework when we see it working in real code.</P><P>To bridge the gap between architectural theory and practical deployment, I have created a hands-on technical walkthrough. I took the standard SAP-provided <A class="" href="https://github.com/SAP-samples/partner-reference-application" target="_blank" rel="noopener nofollow noreferrer">Partner Reference Application (PRA)</A>āthe gold standard sample for complex BTP multitenant SaaS engineeringāand forked it to demonstrate exactly how to transition a standard CAP application into a declarative, CAP Operator-managed deployment.</P><P>You can find the complete implementation and step-by-step repository here: <span class="lia-unicode-emoji" title=":backhand_index_pointing_right:">š</span><STRONG><A href="https://github.com/ElectronicWizzard/partner-reference-application/blob/kymapra1/BLOG.md" target="_self" rel="nofollow noopener noreferrer">ElectronicWizzard/partner-reference-application (kymapra1 branch)</A> </STRONG></P><H2 id="toc-hId-1599685858">Why the Partner Reference Application?</H2><P>The Partner Reference Application is designed to showcase enterprise-grade SaaS development on SAP BTP. It contains complex real-world challenges:</P><UL><LI>Multitenancy with isolated data access.</LI><LI>Integration with BTP core services (XSUAA, Destination, SaaS Provisioning Service).</LI><LI>Explicit lifecycle tasks for tenant onboarding and offboarding.</LI></UL><P>By migrating this specific application to the CAP Operator, we demonstrate that the operator isn't just for simple "Hello World" microservices. It is fully capable of managing sophisticated, production-grade enterprise architectures.</P><P>Here is a glimpse of the migration workflow detailed in the hands-on guide:</P><H3 id="toc-hId-1532255072">1. Project Preparation & Plugin Integration</H3><P>Instead of writing complex Kubernetes deployment manifests from scratch, we leverage the tooling built into the CAP ecosystem. By injecting the development dependency into the project root:</P><DIV class=""><DIV class=""><DIV class=""><PRE><CODE>npm add @cap-js/cap-operator-plugin -D</CODE></PRE></DIV></DIV></DIV><P>We unlock the plugin capabilities that assist in automatically deriving the necessary configuration files and container patterns directly from our existing <CODE>cds</CODE> models and configuration schema.</P><H3 id="toc-hId-1335741567">2. Containerization and Registry Push</H3><P>As container-native deployments are standard practice, the workflow guides you through leveraging Docker to containerize the CAP application services and push them to a container registry accessible by your Kyma cluster.</P><H3 id="toc-hId-1139228062">3. Transitioning to Custom Resources (CRDs)</H3><P>The heart of the transformation lies in replacing standard Kubernetes <CODE>Deployments</CODE> and <CODE>StatefulSets</CODE> with custom resources defined by the CAP Operator, such as <CODE>CAPApplication</CODE>.</P><P>Instead of writing custom logic to handle <CODE>mtxs</CODE> (multitenancy/extensibility) operations during tenant registration, you simply declare the application's characteristics in a clean, unified manifest. The CAP Operator watches this resource and automatically sets up:</P><UL><LI>The necessary application pods.</LI><LI>Automated routing rules.</LI><LI>Secure BTP Services integration using the SAP BTP Service Operator.</LI><LI>Automated tenant provisioning</LI><LI>...</LI></UL><H2 id="toc-hId-813631838">Prerequisites to Get Started</H2><P>To follow along with the tutorial and deploy the project yourself, make sure you have the following environments prepared:</P><OL><LI><P><STRONG>A Kyma Cluster with the CAP Operator Installed:</STRONG> Follow the setup detailed in the <A class="" href="https://community.sap.com/t5/technology-blog-posts-by-sap/kyma-evolution-transforming-sap-kyma-into-a-tailor-made-saas-platform-for/ba-p/14317418" target="_blank">Kyma Evolution series</A> to ensure your cluster control plane is ready to interpret the custom CAP resources.</P></LI><LI><P><STRONG>BTP Provider Subaccount:</STRONG> A dedicated provider subaccount mapped to your Kyma namespace and entitlement of the required services.</P></LI><LI><P><STRONG>Local Tooling:</STRONG> Docker installed on your machine and access to a container registry (e.g., Docker Hub, GitHub Packages).</P></LI></OL><H2 id="toc-hId-617118333">Conclusion & Next Steps</H2><P>Shifting from imperative scripting to a declarative, operator-driven cloud runtime is a massive step forward for building resilient, clean-core SaaS applications on SAP BTP. By analyzing the changes in this fork, you will see exactly how much boilerplate code and deployment complexity can be stripped away from your project lifecycle.</P><P>Head over to the repository, check out the <CODE>kymapra1</CODE> branch, and walk through the implementation: <span class="lia-unicode-emoji" title=":link:">š</span> <STRONG><A href="https://github.com/ElectronicWizzard/partner-reference-application/blob/kymapra1/BLOG.md" target="_blank" rel="noopener nofollow noreferrer">Read the Full Deployment Guide on GitHub</A></STRONG></P><P>For those who want to start with there own workload I would like to recommend the new <A href="https://developers.sap.com/tutorial-navigator.html?tag=software-product%3Atechnology-platform%2Fsap-cap-operator-kubernetes-environment" target="_self" rel="noopener noreferrer">SAP Tutorial for the CAP Operator on the Kyma Environment.</A></P><P>I would love to hear your thoughts! Have you started experimenting with the CAP Operator in your own Kyma or Kubernetes environments? What challenges or successes have you encountered during the shift to declarative operations? Let's discuss in the comments below.</P><P> </P>2026-05-21T15:34:01.526000+02:00https://community.sap.com/t5/sap-for-utilities-blog-posts/atul-architecture-3-bridging-ot-and-it-delivering-mission-critical-grid/ba-p/14409130ATUL Architecture #3: Bridging OT and IT: Delivering Mission-Critical Grid Data to SAP BTP Safely, S2026-06-08T10:52:29.828000+02:00Atul_Joshi85https://community.sap.com/t5/user/viewprofilepage/user-id/2274193<H1 id="toc-hId-1687429205">ATUL Architecture #3: Bridging OT and IT: Delivering Mission-Critical Grid Data to SAP BTP Safely, Scalably, and Cleanly</H1><H3 id="toc-hId-1749081138"><STRONG>1. </STRONG><STRONG>Introduction</STRONG></H3><P>Utilities are accelerating their digital transformation, but one architectural frontier remains notoriously difficult to conquer:</P><P><STRONG>How do you safely ingest high-frequency Operational Technology (OT) data from substations, feeders, and Intelligent Electronic Devices (IEDs) into SAP Business Technology Platform (BTP) without introducing cyber vulnerabilities or disrupting grid reliability?</STRONG></P><P>Legacy architecture often relied on brittle, custom-coded point-to-point connections or heavy middleware that violated basic security isolation principles. This article provides a comprehensive, production-ready, architecture-driven blueprint for securely integrating:</P><UL><LI><STRONG>IEC 61850</STRONG> substation telemetry (GOOSE/MMS)</LI><LI><STRONG>MQTT/Sparkplug B</STRONG> telemetry streams</LI><LI><STRONG>SCADA / RTU / IED</STRONG> asynchronous events</LI><LI><STRONG>Grid-edge IoT</STRONG> sensors</LI></UL><P>We will achieve this by leverage modern <STRONG>event-driven architectures (EDA)</STRONG>, <STRONG>Zero-Trust Network Access (ZTNA)</STRONG>, and SAP <STRONG>Clean Core</STRONG> design patterns.</P><H3 id="toc-hId-1552567633"><STRONG>2. </STRONG><STRONG>Why OT-to-IT Integration Challenges Enterprise</STRONG></H3><P>Bridging the physical grid and the digital cloud requires reconciling two fundamentally opposing engineering philosophies:</P><TABLE><TBODY><TR><TD><P><STRONG>Dimension</STRONG></P></TD><TD><P><STRONG>Operational Technology (OT)</STRONG></P></TD><TD><P><STRONG>Enterprise Information Technology (IT)</STRONG></P></TD></TR><TR><TD><P><STRONG>Primary Priority</STRONG></P></TD><TD><P><STRONG>Availability & Safety:</STRONG> Human life and grid stability. Zero downtime tolerated.</P></TD><TD><P><STRONG>Confidentiality & Integrity:</STRONG> Data security, compliance, and financial accuracy.</P></TD></TR><TR><TD><P><STRONG>Protocols</STRONG></P></TD><TD><P>Industrial, low-overhead, non-IP, or deterministic (IEC 61850, DNP3, Modbus).</P></TD><TD><P>Web-native, human-readable, RESTful (HTTPS, OData, gRPC).</P></TD></TR><TR><TD><P><STRONG>Network Philosophy</STRONG></P></TD><TD><P>Air-gapped, strictly segmented (Purdue Model Level 0-3), highly localized.</P></TD><TD><P>Cloud-native, perimeterless, distributed, global access.</P></TD></TR><TR><TD><P><STRONG>Data Characteristics</STRONG></P></TD><TD><P>Ultra-high frequency (milliseconds), time-series bursts, massive velocity.</P></TD><TD><P>Transactional, structured, stateful, request-response.</P></TD></TR></TBODY></TABLE><P>To bridge this gap safely, cloud ingestion must be completely <STRONG>outbound-only</STRONG> from the lower levels, strictly audited, cryptographically verified, and decoupled via an enterprise event broker.</P><H3 id="toc-hId-1356054128"><STRONG>3. </STRONG><STRONG>Business impact on Utilities</STRONG></H3><P>This architecture is not just a technical modernizationāit directly enables:</P><UL><LI>Reduced unplanned outages through real-time anomaly detection</LI><LI>Lower maintenance costs via predictive asset health insights</LI><LI>Improved regulatory compliance with secure audit-ready telemetry flows</LI><LI>Faster field response through automated S/4HANA work order generation</LI><LI>Scalable grid-edge onboarding without re-platforming legacy OT systems</LI></UL><H3 id="toc-hId-1159540623"><STRONG>4. </STRONG><STRONG>Reference Architecture: Purdue Model to SAP BTP</STRONG></H3><P>The architecture below maps the telemetry flow across the classic <STRONG>Purdue Model for Industrial Control Systems</STRONG>, ensuring that no direct connection is ever established between the cloud and Level 2/3 critical infrastructure.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Atul_Joshi85_0-1780369948895.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/417109i717E3E49222AF87C/image-size/medium?v=v2&px=400" role="button" title="Atul_Joshi85_0-1780369948895.png" alt="Atul_Joshi85_0-1780369948895.png" /></span></P><H3 id="toc-hId-963027118"><STRONG>5. </STRONG><STRONG>Architectural Safeguards Highlighted:</STRONG></H3><UL><LI><STRONG>Directional Isolation:</STRONG> Firewalls block all inbound traffic to the Substation OT network. The Industrial Edge Gateway establishes an <EM>outbound</EM> connection to the Upper DMZ broker.</LI><LI><STRONG>Decoupling Layer:</STRONG> <STRONG>SAP Advanced Event Mesh (AEM)</STRONG> acts as the shock absorber, ensuring that high-frequency grid telemetry bursts do not overwhelm downstream ERP APIs.</LI></UL><P><FONT color="#993366"><STRONG><FONT face="andale mono,times" size="4">*Architectural Principle: Direct OT-to-cloud integrations without DMZ brokers are inherently unsafe. Any architecture that bypasses the industrial DMZ violates fundamental Purdue Model isolation and must be avoided.</FONT></STRONG></FONT></P><H3 id="toc-hId-766513613"><STRONG>6. </STRONG><STRONG>Deep-Dive: Protocol Translation & Topic Design</STRONG></H3><P><STRONG> </STRONG><STRONG>The Conversion: IEC 61850 to Lightweight Cloud Events: </STRONG>IEC 61850 uses Manufacturing Message Specification (MMS) for complex reporting and Generic Object-Oriented Substation Events (GOOSE) for peer-to-peer microsecond trips. These cannot be parsed by cloud apps directly.</P><P>An Edge Gateway maps these complex hierarchical Logical Nodes (e.g., MMXU1 for Three-Phase Voltage/Current) into standard JSON structures wrapped in the <STRONG>CloudEvents</STRONG> specification.</P><P><STRONG>Production-Grade Ingestion Payload Example</STRONG></P><P>Below is the exact schema passed from the Upper DMZ to SAP Advanced Event Mesh when a transformer experiences a thermal anomaly:</P><P>JSON { "specversion": "1.0",</P><P> "id": "evt-773a-4b92-88ef-22114949ad45", "source": "/us-east/substation-s12/transformer-tx03", "type":</P><P>"com.utility.grid.telemetry.v1",</P><P> "datacontenttype": "application/json",</P><P> "time": "2026-06-01T18:21:55.003Z",</P><P> "data": { "logical_node": "YPTR1",</P><P> "component": "WindingTemperature",</P><P> "metrics": { "temperature_celsius": 98.4,</P><P> "ambient_celsius": 32.1,</P><P> "cooling_fan_status": "ON_MAX" },</P><P> "status": { "health_score": 72.5,</P><P> "anomaly_detected": true,</P><P> "severity": "WARNING" } } }</P><P><STRONG>Enterprise Topic Hierarchy Best Practices</STRONG></P><P>To optimize routing efficiency within SAP Advanced Event Mesh and avoid bloated subscription wildcards, we enforce a structured, predictable topic hierarchy layout:</P><P>$$\text{Domain} \,/\, \text{Business Unit} \,/\, \text{Geographic Zone} \,/\, \text{Substation ID} \,/\, \text{Asset Category} \,/\, \text{Asset ID} \,/\, \text{Event Type}$$</P><P><STRONG>Examples:</STRONG></P><UL><LI>grid/distribution/northeast/sub-s12/transformer/tx03/telemetry</LI><LI>grid/transmission/midwest/sub-s45/circuit-breaker/cb07/state</LI></UL><P>This allows BTP consumers to subscribe with surgical precision. For instance, a predictive maintenance engine can listen to all transformer telemetry across the entire Northeast region using the pattern: grid/distribution/northeast/+/transformer/+/telemetry.</P><H3 id="toc-hId-570000108"><STRONG>7. </STRONG><STRONG>The Blueprint in Action: A Practitioner Scenario</STRONG></H3><P>To understand how this architecture operates under pressure, let's trace a real-world grid event: <STRONG>A severe thermal anomaly on Substation Transformer TX03 located in the Northeast distribution zone.</STRONG></P><P><STRONG>Step 1: The Edge Trigger (Purdue Level 2)</STRONG></P><OL><LI>The physical temperature sensor on Transformer TX03 detects a sudden spike, hitting $98.4^\circ\text{C}$.</LI><LI>The local Intelligent Electronic Device (IED) broadcasts this telemetry via an IEC 61850 MMS (Manufacturing Message Specification) reporting block over the substation's internal fiber optic bus.</LI></OL><P><STRONG>Step 2: Protocol Translation & Hardening (Industrial DMZ Level 3.5)</STRONG></P><OL><LI>The OT Industrial Edge Gateway (running Litmus Edge) intercepts the raw MMS packet.</LI><LI>It strips out the heavy industrial headers, extracts the data fields from the Logical Node YPTR1 (the standard IEC 61850 node for a power transformer), and maps them into a standardized, web-friendly JSON payload.</LI><LI>The gateway packages this payload inside a standardized CloudEvents v1.0 wrapper.</LI></OL><P><STRONG>Step 3: Secure Egress to the DMZ Proxy (Enterprise DMZ Level 3.5)</STRONG></P><OL><LI>The Edge Gateway initiates an <STRONG>outbound-only TLS 1.3</STRONG> connection to the Upper DMZ MQTT Broker (HiveMQ Cluster).</LI><LI>It passes hardware-secured <STRONG>mTLS client certificates</STRONG> to prove its identity and publishes the message to the strict, isolated topic:</LI></OL><P> āgrid/distribution/northeast/sub-s12/transformer/tx03/telemetryā</P><P><STRONG>Step 4: Ingestion by SAP Advanced Event Mesh (SAP BTP Layer)</STRONG></P><OL><LI>The HiveMQ cluster securely routes the incoming stream over a secure WebSocket connection into <STRONG>SAP Advanced Event Mesh (AEM)</STRONG> via Dynamic Message Routing (DMR).</LI><LI>Because AEM natively understands the hierarchical topic layout, it instantly clones and routes the message to three concurrent, decoupled subscribers without any database lag:</LI><UL><LI><STRONG>SAP Datasphere Ingestion Queue:</STRONG> For long-term historical time-series storage and grid analytics.</LI><LI><STRONG>SAP AI Core Engine:</STRONG> To recalculate the transformer's Remaining Useful Life (RUL) score.</LI><LI><STRONG>CAP Event Processor Queue:</STRONG> Dedicated to immediate, critical rule evaluation.</LI></UL></OL><P><STRONG>Step 5: Real-Time Orchestration & Clean Core Action</STRONG></P><OL><LI>A lightweight <STRONG>SAP CAP (Cloud Application Model)</STRONG> microservice consumes the message from its dedicated AEM queue.</LI><LI>The service executes an automated rule: <EM>If temperature_celsius > $95^\circ\text{C}$ and cooling_fan_status == "ON_MAX", trigger emergency field intervention.</EM></LI><LI>Rather than writing complex custom ABAP tables inside the ERP, the CAP service uses the standard <STRONG>SAP Integration Suite</STRONG> to call the out-of-the-box S/4HANA OData v4 API (API_EQUIPMENT_SRV).</LI><LI><STRONG>The Result:</STRONG> An automated <STRONG>Urgent Corrective Maintenance Work Order</STRONG> is instantly generated in <STRONG>SAP S/4HANA Asset Management</STRONG> with a high-priority dispatch flagāall accomplished within 2.3 seconds of the physical temperature spike, leaving the digital core clean, secure, and upgrade-ready.</LI></OL><H3 id="toc-hId-373486603"><STRONG>8. </STRONG><STRONG>Advanced Substation-to-BTP Integration Patterns</STRONG></H3><P> <FONT color="#993366">*Reality Check*: Batch ingestion architectures are fundamentally incompatible with modern grid telemetry. Millisecond-level data streams require real-time, event-driven processing modelsānot delayed batch pipelines.</FONT></P><P>Depending on your edge compute availability and governance requirements, choose one of these production deployment topologies:</P><P><STRONG>Pattern A: Multi-Tiered Message Broker Architecture (Recommended for Zero-Trust)</STRONG></P><UL><LI><STRONG>Mechanism:</STRONG> An Edge Gateway pushes local data to an on-premises DMZ MQTT cluster (e.g., Hive MQ). Secure bridge configuration clones and forwards permitted topics out to <STRONG>SAP Advanced Event Mesh (AEM)</STRONG> via cloud connectors or secure WebSocketās.</LI><LI><STRONG>Pros:</STRONG> Strict isolation; if the cloud connection drops, the local DMZ broker caches events, mitigating data loss.</LI></UL><P><STRONG>Pattern B: Intelligent Edge Compute with Stream Processing (Recommended for High Frequency)</STRONG></P><UL><LI><STRONG>Mechanism:</STRONG> Deploy an industrial edge compute node (running lightweight runtimes like Node-RED or KubeEdge) physically inside the substation. It aggregates millisecond telemetry, calculates rolling 1-minute means/standard deviations, and publishes <EM>only</EM> summarized statistical health events to SAP BTP.</LI><LI><STRONG>Pros:</STRONG> Radically reduces egress data costs and avoids overwhelming BTP with trivial data points.</LI></UL><H3 id="toc-hId-176973098"><STRONG>9. </STRONG><STRONG>Security & Governance Blueprint (Non-Negotiable Controls)</STRONG></H3><P>Bringing OT data to the cloud is impossible without an airtight security architecture. Implement the following parameters at each layer:</P><UL><LI><STRONG>Mutual TLS (mTLS) with Hardware Roots of Trust:</STRONG> Every edge gateway must authenticate against the DMZ broker using X.509 client certificates issued by an internal Utility Certificate Authority (CA). Private keys must be stored in secure TPM 2.0 hardware chips on the edge device.</LI><LI><STRONG>Granular Access Control Lists (ACLs):</STRONG> Edge gateways must be explicitly barred from publishing to topics outside their assigned asset scope. A compromise of Substation A must never allow injection of malicious events into Substation B.</LI><LI><STRONG>Schema Validation at the Ingress Gate:</STRONG> Use the Schema Validation features in SAP Advanced Event Mesh. If a message arrives containing bad structures, malformed types, or executable code payloads, it is immediately shunted to a <STRONG>Dead Letter Queue (DLQ)</STRONG> for forensics, blocking code injection vectors.</LI><LI><STRONG>Network Partitioning & Backpressure Controls:</STRONG> If the connection between the utility DMZ and SAP BTP degrades, the DMZ broker must utilize Local Storage Buffering. High QoS (Quality of Service) 1 or 2 settings must be applied strictly to critical operational state changes (e.g., breaker trips) to guarantee delivery, while standard continuous metrics use QoS 0 to save bandwidth.</LI></UL><H3 id="toc-hId--94771776"><STRONG>10. </STRONG><STRONG>Realizing Enterprise Value in SAP BTP</STRONG></H3><P>Once normalized grid events stream cleanly into SAP Advanced Event Mesh, they become actionable assets without touching the digital core directly:</P><P><FONT color="#993366">*Strategic Insight*: Event Mesh is not optionalāit is the control plane for grid-scale ingestion. Without event-driven decoupling, high-frequency OT telemetry cannot be safely or scalable integrated into enterprise systems.</FONT></P><P><STRONG>The Event Consumer Microservices Layer</STRONG></P><P>Deploy lightweight, stateless microservices within the <STRONG>SAP BTP Kyma Runtime</STRONG> or <STRONG>SAP Cloud Application Model (CAP)</STRONG> to process incoming streams:</P><P> </P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Atul_Joshi85_1-1780369948938.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/417110iE90430738CB44DA7/image-size/medium?v=v2&px=400" role="button" title="Atul_Joshi85_1-1780369948938.png" alt="Atul_Joshi85_1-1780369948938.png" /></span></P><P> </P><P> </P><UL><LI><STRONG>Real-Time Grid Insights:</STRONG> Kyma functions scan the event properties. If a transformer temperature exceeds defined thresholds (temperature_celsius > 95), an immediate event is triggered.</LI><LI><STRONG>AI-Driven Asset Health Optimization:</STRONG> Data streams scale out directly into <STRONG>SAP Datasphere</STRONG> and <STRONG>SAP AI Core</STRONG>. Machine Learning models calculate remaining useful life (RUL) metrics based on historical load/thermal characteristics, sending updated health indices back to operational dashboards.</LI></UL><P><STRONG>The Clean Core Imperative</STRONG></P><P>By processing events asynchronously in BTP, the core enterprise system (<STRONG>SAP S/4HANA / IS-U</STRONG>) remains entirely untouched by heavy custom code. S/4HANA only participates when action is requiredāsuch as creating an asset maintenance order or executing a field service dispatch via standard standard OData v4 APIs. The system updates and upgrades effortlessly, unencumbered by massive custom databases full of RAW time-series data.</P><H3 id="toc-hId--291285281"><STRONG>11. </STRONG><STRONG>End-to-End Flow: Transformer Overheating Scenario</STRONG></H3><UL><LI>A transformer in Substation S12 exceeds thermal threshold (98.4°C).</LI><LI>IEC 61850 MMS reports are captured by Edge Gateway.</LI><LI>Gateway converts payload to Cloud Events JSON.</LI><LI>Event is securely published (mTLS) to DMZ MQTT broker.</LI><LI>Broker forwards allowed topics to SAP Advanced Event Mesh.</LI><LI>AEM routes the event to a CAP microservice.</LI><LI>CAP evaluates rules ā triggers anomaly event.</LI><LI>SAP AI Core enriches with predictive health score.</LI><LI>SAP S/4HANA automatically creates a maintenance work order.</LI><LI>Field service team is dispatched proactively.</LI></UL><H3 id="toc-hId--487798786"><STRONG>12. </STRONG><STRONG>Operational Summary Blueprint</STRONG></H3><OL><LI>The convergence of OT and IT is no longer optionalāit is foundational to modern utilities.</LI><LI>However, success depends not on connectivity alone, but on *how* that connectivity is governed. </LI><LI>Architectures that prioritize strict isolation, event-driven decoupling, and Clean Core principles will define the next generation of grid intelligence.</LI><LI>SAP BTP, when used correctly, becomes not just an integration platformābut the control tower for real-time, autonomous utility operations.</LI></OL><P> </P>2026-06-08T10:52:29.828000+02:00https://community.sap.com/t5/technology-blog-posts-by-members/cap-now-supports-reactjs-with-working-demo-ui5-web-components/ba-p/14412493CAP Now Supports ReactJS - With working demo + UI5 Web components2026-06-09T10:01:53.932000+02:00chrisobarhttps://community.sap.com/t5/user/viewprofilepage/user-id/845878<P>Based on the latest CAP update in the official docs, React and VueJS are now fully supported as frontend options. I tested the setup endātoāend based on the official docs link and prepared a working demo in BTP Trial.</P><P>Reference links:</P><UL><LI><A title="CAP-React-Vue Example Project" href="https://cap.cloud.sap/docs/guides/uis/vue-react#example-project" target="_blank" rel="nofollow noopener noreferrer">CAP-React-Vue Example Project</A> </LI><LI><A title="UI5 Web Components for React - Docs" href="https://ui5.github.io/webcomponents-react/v2/" target="_blank" rel="nofollow noopener noreferrer">UI5 Web Components for React - Docs</A> </LI></UL><P>Anyhow, to start with, the blog shall include:</P><OL><LI>Creating Bookshop CAP and adding React app</LI><LI>Installing UI5 Web for React dependencies</LI><LI>Local testing</LI><LI>Setting up IAS and HANA deployment</LI></OL><P>Pre requisites:</P><UL><LI><a href="https://community.sap.com/t5/user/viewprofilepage/user-id/2302137">@SAP</a>/cds-dk library should be at least 9^ or latest version. </LI><LI>CAP version should be latest</LI><LI>Your BTP Subaccount is subscribed to SAP HANA Cloud with instance</LI><LI>Your BTP Subaccount is subscribed to CIS and Application Frontend Service with instances</LI></UL><P><EM>To ensure latest CAP version, use BAS</EM></P><P><STRONG>1. Create Bookshop CAP and adding React app</STRONG></P><UL><LI>Log-in to your BAS or VS-code</LI><LI>Open the terminal and enter the command to initiate CAP sample project </LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="chrisobar_0-1780760775364.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/418536iF41E0B1C16C6489A/image-size/medium?v=v2&px=400" role="button" title="chrisobar_0-1780760775364.png" alt="chrisobar_0-1780760775364.png" /></span></P><UL><LI>initiate adding react application using this command <STRONG>cds add react --into <your-react-app></STRONG></LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="chrisobar_3-1780761055755.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/418539i8C0C2865961E1427/image-size/medium?v=v2&px=400" role="button" title="chrisobar_3-1780761055755.png" alt="chrisobar_3-1780761055755.png" /></span></P><P><STRONG>2. Installing UI5 Web Components for React dependencies</STRONG></P><UL><LI>Open the terminal, go to your app folder</LI><LI>As per the official doc, enter the following commands:</LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="chrisobar_4-1780761149185.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/418540iB7ED313BB2709F62/image-size/medium?v=v2&px=400" role="button" title="chrisobar_4-1780761149185.png" alt="chrisobar_4-1780761149185.png" /></span></P><UL><LI>Utilize UI5 elements. (See my example below):</LI></UL><P><STRONG>App.jsx</STRONG></P><pre class="lia-code-sample language-javascript"><code>import { Bar, Card, Label, Page, Table, TableCell, TableHeaderCell, TableHeaderRow, TableRow, Title } from '@ui5/webcomponents-react'
import { useEffect, useState } from 'react'
export default function App() {
const [books, setBooks] = useState([])
useEffect(() => {
fetch('/odata/v4/catalog/Books')
.then(r => r.json())
.then(r => setBooks(r.value))
}, [])
return (
<>
<Page backgroundDesign='Solid'
header={<Bar design='Header'>
<Title>Books</Title>
</Bar>}
style={{ width: '100vw', height: '100vh' }}
>
<Card style={{ width: '150px', height: '150px', marginBottom: '3rem', position: 'relative', marginTop: '3rem' }}>
<Label style={{ padding: '1rem'}}>Total Book count:</Label>
<Title size='H1' style={{ padding: '1rem', position: 'absolute', bottom: '1%', textAlign: 'right' }}>{books.length}</Title>
</Card>
<Table headerRow={<TableHeaderRow sticky>
<TableHeaderCell>Title</TableHeaderCell>
<TableHeaderCell>Author</TableHeaderCell>
</TableHeaderRow>}>
{books.map(b=>(
<TableRow key={b.ID}>
<TableCell key={b.ID}>
<span>{b.title}</span>
</TableCell>
<TableCell>
<span><i> by {b.author}</i></span>
</TableCell>
</TableRow>
))}
</Table>
</Page>
</>
)
}</code></pre><P><STRONG>3. Local Testing</STRONG></P><UL><LI>Run the app (cds watch)</LI></UL><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="chrisobar_7-1780761332652.png" style="width: 835px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/418544i5DD023E325B3ACED/image-dimensions/835x474?v=v2" width="835" height="474" role="button" title="chrisobar_7-1780761332652.png" alt="chrisobar_7-1780761332652.png" /></span></P><P><STRONG>4. Setting up IAS and HANA Deployment</STRONG></P><P><EM>As per official document, you just need the following commands for deployment</EM></P><UL><LI>cds add app-frontend</LI><LI>cds add ias <EM>or </EM>cds add xsuaa</LI><LI>cds add hana</LI><LI>cds up</LI></UL><P><EM><STRONG>cds up</STRONG> will automatically create the .mta file and deploy the CAP, React App, IAS and HANA deployer instances</EM></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="chrisobar_8-1780761694711.png" style="width: 832px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/418545iA0FF1E635553436E/image-dimensions/832x181?v=v2" width="832" height="181" role="button" title="chrisobar_8-1780761694711.png" alt="chrisobar_8-1780761694711.png" /></span></P><P><STRONG>Conclusion:</STRONG></P><P><SPAN>With all these pieces in place, you now have a full CAP + React setup running endātoāend, from local development to IAS authentication and HANA deployment. This approach gives you the flexibility of a modern frontend while still taking advantage of CAPās service layer, security model, and deployment tooling. Feel free to extend the sample, plug in your own UI components, or adapt the structure for your next project.</SPAN></P><P><SPAN>Let me know if you have any further questions or wish to communicate, just comment below.</SPAN></P><P> </P><DIV> </DIV><P> </P><P> </P><P> </P><P> </P>2026-06-09T10:01:53.932000+02:00https://community.sap.com/t5/technology-blog-posts-by-members/calling-the-sap-business-partner-api-from-kyma-without-serverless/ba-p/14415928Calling the SAP Business Partner API from Kyma without Serverless2026-06-10T22:01:00.221000+02:00neilaspinhttps://community.sap.com/t5/user/viewprofilepage/user-id/167493<H2 id="toc-hId-1817323946">Introduction</H2><P>My <A href="https://community.sap.com/" target="_blank">previous post</A> walked through calling the SAP Business Partner API from a Kyma Serverless Function. This post covers what happens when that approach hits real-world constraints ā and how to adapt.</P><P>The two problems I encountered on a managed Kyma cluster:</P><OL><LI><STRONG>The Serverless module wasn't installed</STRONG> ā <CODE>kind: Function</CODE> doesn't exist as a resource type, so the function YAML simply won't apply</LI><LI><STRONG>No permission to create BTP Destinations via the cockpit</STRONG> ā the New Destination button was greyed out</LI></OL><P>Both are common in corporate and training environments where you don't own the cluster or subaccount. Here's how to work around both.</P><P>By the end you'll have the same result as the original post ā a live endpoint returning SAP Business Partner JSON ā but deployed as a standard Kubernetes Deployment instead of a Kyma Function.</P><HR /><H2 id="toc-hId-1620810441">What Changed vs the Original Approach</H2><P>Original This Post</P><TABLE><TBODY><TR><TD><CODE>kind: Function</CODE> (Kyma Serverless)</TD><TD><CODE>kind: Deployment</CODE> (standard Kubernetes)</TD></TR><TR><TD>Code inline in YAML</TD><TD>Code in a Docker container</TD></TR><TR><TD>No registry needed</TD><TD>Image pushed to GitHub Container Registry</TD></TR><TR><TD>Destination created in BTP cockpit</TD><TD>Destination created via Destination Service REST API</TD></TR></TBODY></TABLE><P>The call chain is identical ā your container still calls XSUAA for a token, resolves the destination, then calls the SAP API sandbox. The difference is purely in how the code is packaged and deployed.</P><HR /><H2 id="toc-hId-1424296936">Prerequisites</H2><UL><LI><CODE>kubectl</CODE> configured against a Kyma cluster</LI><LI>Docker installed locally</LI><LI>A GitHub account (for the container registry)</LI><LI>An account on <A href="https://api.sap.com/" target="_blank" rel="noopener noreferrer">api.sap.com</A> with an API key</LI></UL><HR /><H2 id="toc-hId-1227783431">Step 1: Check What's Actually Available</H2><P>Before spending time on a Kyma Function approach, verify whether Serverless is installed:</P><PRE><CODE>kubectl api-resources | grep serverless</CODE></PRE><P>If nothing is returned, the Serverless module isn't enabled. You'll need either cluster admin access to enable it, or the approach in this post.</P><P>Also verify the BTP Service Operator is available (needed for ServiceInstance and ServiceBinding):</P><PRE><CODE>kubectl api-resources | grep services.cloud.sap.com</CODE></PRE><P>You should see <CODE>serviceinstances</CODE> and <CODE>servicebindings</CODE>. If these are missing, stop here ā the BTP Service Operator isn't installed and none of this will work.</P><HR /><H2 id="toc-hId-1031269926">Step 2: Create the Destination Service Instance and Binding</H2><P>This is identical to the original post. Apply these to your namespace:</P><P><STRONG>k8s/destination-instance.yaml</STRONG></P><PRE><CODE>apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: destination-service
namespace: dev-space-neil
spec:
serviceOfferingName: destination
servicePlanName: lite</CODE></PRE><P><STRONG>k8s/destination-binding.yaml</STRONG></P><PRE><CODE>apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: destination-service-binding
namespace: dev-space-neil
spec:
serviceInstanceName: destination-service</CODE></PRE><PRE><CODE>kubectl apply -f k8s/destination-instance.yaml
kubectl apply -f k8s/destination-binding.yaml</CODE></PRE><P>Wait until both show <CODE>Ready: True</CODE>:</P><PRE><CODE>kubectl get serviceinstance,servicebinding -n dev-space-neil</CODE></PRE><P>The binding creates a Kubernetes secret called <CODE>destination-service-binding</CODE> containing <CODE>clientid</CODE>, <CODE>clientsecret</CODE>, <CODE>url</CODE> (XSUAA token endpoint), and <CODE>uri</CODE> (Destination Service endpoint). Your container will read these as environment variables.</P><HR /><H2 id="toc-hId-834756421">Step 3: Create the BTP Destination via REST API</H2><P>If you have access to the BTP cockpit, create the destination there as described in the original post. If not, use the Destination Service REST API directly ā the service binding credentials you just created have enough access to create instance-level destinations.</P><P>Get a token and POST the destination:</P><PRE><CODE>TOKEN=$(kubectl get secret destination-service-binding -n dev-space-neil \
-o jsonpath='{.data.clientid}' | base64 -d)
SECRET=$(kubectl get secret destination-service-binding -n dev-space-neil \
-o jsonpath='{.data.clientsecret}' | base64 -d)
XSUAA=$(kubectl get secret destination-service-binding -n dev-space-neil \
-o jsonpath='{.data.url}' | base64 -d)
URI=$(kubectl get secret destination-service-binding -n dev-space-neil \
-o jsonpath='{.data.uri}' | base64 -d)
ACCESS_TOKEN=$(curl -s -X POST "$XSUAA/oauth/token" \
-u "$TOKEN:$SECRET" \
-d "grant_type=client_credentials" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
curl -X POST "$URI/destination-configuration/v1/instanceDestinations" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Name": "S4H_SANDBOX_BP",
"Type": "HTTP",
"URL": "https://sandbox.api.sap.com/s4hanacloud/sap/opu/odata/sap/API_BUSINESS_PARTNER",
"Authentication": "NoAuthentication",
"ProxyType": "Internet",
"APIKey": "<your-api.sap.com-key>"
}'</CODE></PRE><P>The distinction between <CODE>subaccountDestinations</CODE> and <CODE>instanceDestinations</CODE> matters here. Subaccount destinations are managed at the BTP subaccount level (requires admin access). Instance destinations are scoped to the service instance and can be created by the binding credentials ā which is what we have.</P><HR /><H2 id="toc-hId-638242916">Step 4: Write the Handler and HTTP Server</H2><P>The business logic in <CODE>handler.js</CODE> is unchanged from the original post ā get a token, resolve the destination, call the API. The only difference is gzip handling (the SAP API sandbox always compresses responses) and reading the API key from the destination properties rather than a separate Kubernetes secret.</P><P><STRONG>handler.js</STRONG></P><PRE><CODE>const https = require("https");
const http = require("http");
const zlib = require("zlib");
/**
* Gets an OAuth2 access token from XSUAA using client credentials flow.
* Credentials are injected as env vars from the destination-service-binding secret.
*/
async function getDestinationToken() {
const credentials = Buffer.from(
`${process.env.CLIENTID}:${process.env.CLIENTSECRET}`
).toString("base64");
const tokenUrl = new URL(`${process.env.XSUAA_URL}/oauth/token`);
return new Promise((resolve, reject) => {
const options = {
hostname: tokenUrl.hostname,
path: `${tokenUrl.pathname}?grant_type=client_credentials`,
method: "POST",
headers: {
Authorization: `Basic ${credentials}`, // Base64-encoded clientid:clientsecret
"Content-Type": "application/x-www-form-urlencoded",
},
};
const req = https.request(options, (res) => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => {
try { resolve(JSON.parse(data).access_token); }
catch (e) { reject(new Error(`Token parse failed: ${data}`)); }
});
});
req.on("error", reject);
req.end();
});
}
/**
* Resolves a named BTP Destination via the Destination Service REST API.
* Returns the full destination object including destinationConfiguration,
* which contains the target URL and any additional properties (e.g. APIKey).
*/
async function getDestination(name, token) {
const destUrl = new URL(
`${process.env.DESTINATION_URI}/destination-configuration/v1/destinations/${name}`
);
return new Promise((resolve, reject) => {
const options = {
hostname: destUrl.hostname,
path: destUrl.pathname,
method: "GET",
headers: { Authorization: `Bearer ${token}` }, // Token obtained from getDestinationToken()
};
const req = https.request(options, (res) => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => {
try { resolve(JSON.parse(data)); }
catch (e) { reject(new Error(`Destination parse failed: ${data}`)); }
});
});
req.on("error", reject);
req.end();
});
}
/**
* Calls the SAP Business Partner API via the resolved destination.
* The SAP API Hub sandbox always returns gzip-compressed responses,
* so we explicitly handle gzip/deflate decompression via Node's zlib module.
*/
async function fetchBusinessPartners(destination) {
const config = destination.destinationConfiguration;
const targetUrl = new URL(`${config.URL}/A_BusinessPartner?$format=json&$top=20`);
// Use http or https depending on the destination URL scheme
const protocol = targetUrl.protocol === "https:" ? https : http;
return new Promise((resolve, reject) => {
const options = {
hostname: targetUrl.hostname,
path: `${targetUrl.pathname}${targetUrl.search}`,
method: "GET",
headers: {
APIKey: config.APIKey, // API key stored as a destination property, not a separate secret
Accept: "application/json",
},
};
const req = protocol.request(options, (res) => {
const chunks = [];
const encoding = res.headers["content-encoding"];
// Pipe through the appropriate decompressor based on Content-Encoding header
const stream =
encoding === "gzip" ? res.pipe(zlib.createGunzip()) :
encoding === "deflate" ? res.pipe(zlib.createInflate()) : res;
stream.on("data", (chunk) => chunks.push(chunk));
stream.on("end", () => {
const raw = Buffer.concat(chunks).toString("utf8");
try { resolve({ statusCode: res.statusCode, body: JSON.parse(raw) }); }
catch (e) { resolve({ statusCode: res.statusCode, body: raw }); }
});
stream.on("error", reject);
});
req.on("error", reject);
req.end();
});
}
/**
* Main entry point ā orchestrates the full call chain:
* 1. Get XSUAA token
* 2. Resolve the S4H_SANDBOX_BP destination
* 3. Fetch Business Partners from the SAP API sandbox
* Returns a response object compatible with both Kyma Functions and the HTTP server wrapper.
*/
module.exports = {
main: async function (event, context) {
try {
const token = await getDestinationToken();
const destination = await getDestination("S4H_SANDBOX_BP", token);
const result = await fetchBusinessPartners(destination);
return {
statusCode: result.statusCode,
headers: { "Content-Type": "application/json" },
body: result.body,
};
} catch (err) {
console.error("Error:", err.message);
return {
statusCode: 500,
headers: { "Content-Type": "application/json" },
body: { error: err.message },
};
}
},
};</CODE></PRE><P>Since this is a container rather than a Kyma Function, we need a simple HTTP server to drive it. <CODE>server.js</CODE> wraps the handler:</P><P><STRONG>server.js</STRONG></P><PRE><CODE>const http = require("http");
const handler = require("./handler");
const PORT = process.env.PORT || 8080; // Default to 8080; override via env var if needed
/**
* Minimal HTTP server that wraps handler.js for container deployment.
* Kyma Functions have their own runtime that calls handler.main() directly ā
* this server provides the equivalent entry point when running as a standard Deployment.
* Every request regardless of path/method triggers the Business Partner fetch.
*/
const server = http.createServer(async (req, res) => {
try {
const result = await handler.main({}, {});
// handler.main() may return body as object or string ā normalise to string
const body = typeof result.body === "string"
? result.body
: JSON.stringify(result.body);
res.writeHead(result.statusCode || 200, {
"Content-Type": "application/json",
...result.headers,
});
res.end(body);
} catch (err) {
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: err.message }));
}
});
server.listen(PORT, () => console.log(`Listening on port ${PORT}`));</CODE></PRE><HR /><H2 id="toc-hId-441729411">Step 5: Build and Push a Multi-Platform Container Image</H2><P>This step doesn't exist in the original Kyma Function approach ā the serverless runtime handles that for you. Here you need to build a Docker image and push it to a registry the cluster can reach.</P><P><STRONG>Dockerfile</STRONG></P><PRE><CODE>FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
COPY handler.js server.js ./
EXPOSE 8080
CMD ["node", "server.js"]</CODE></PRE><P>One important detail: if you're on an Apple Silicon Mac, Docker builds <CODE>linux/arm64</CODE> by default. Most cloud Kubernetes clusters run <CODE>linux/amd64</CODE>. Build for both to avoid an <CODE>ImagePullBackOff</CODE> with the error <CODE>no match for platform in manifest</CODE>:</P><PRE><CODE># Authenticate with GitHub Container Registry
echo $(gh auth token) | docker login ghcr.io -u <your-github-username> --password-stdin
# Build and push for both platforms
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t ghcr.io/<your-github-username>/bp-function:latest \
--push .</CODE></PRE><P>Make the package public in GitHub (your profile ā Packages ā bp-function ā Package settings ā Change visibility ā Public) so the cluster can pull it without credentials.</P><HR /><H2 id="toc-hId-245215906">Step 6: Deploy to Kyma</H2><P><STRONG>k8s/deployment.yaml</STRONG></P><PRE><CODE>apiVersion: apps/v1
kind: Deployment
metadata:
name: bp-function
namespace: dev-space-neil
labels:
app: bp-function
spec:
replicas: 1
selector:
matchLabels:
app: bp-function
template:
metadata:
labels:
app: bp-function
spec:
containers:
- name: bp-function
image: ghcr.io/<your-github-username>/bp-function:latest
ports:
- containerPort: 8080
env:
- name: CLIENTID
valueFrom:
secretKeyRef:
name: destination-service-binding
key: clientid
- name: CLIENTSECRET
valueFrom:
secretKeyRef:
name: destination-service-binding
key: clientsecret
- name: XSUAA_URL
valueFrom:
secretKeyRef:
name: destination-service-binding
key: url
- name: DESTINATION_URI
valueFrom:
secretKeyRef:
name: destination-service-binding
key: uri
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: bp-function
namespace: dev-space-neil
spec:
selector:
app: bp-function
ports:
- port: 80
targetPort: 8080</CODE></PRE><P><STRONG>k8s/apirule.yaml</STRONG></P><P>Use the full cluster domain rather than a short hostname. Find yours with:</P><PRE><CODE>kubectl get configmap shoot-info -n kube-system -o jsonpath='{.data.domain}'</CODE></PRE><PRE><CODE>apiVersion: gateway.kyma-project.io/v2
kind: APIRule
metadata:
name: bp-function
namespace: dev-space-neil
spec:
hosts:
- bp-function.<your-cluster-domain>.kyma.ondemand.com
service:
name: bp-function
port: 80
gateway: kyma-system/kyma-gateway
rules:
- path: /*
methods:
- GET
noAuth: true</CODE></PRE><P>Apply both:</P><PRE><CODE>kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/apirule.yaml</CODE></PRE><P>Check the pod is running:</P><PRE><CODE>kubectl get pods -n dev-space-neil -l app=bp-function</CODE></PRE><HR /><H2 id="toc-hId-48702401">Step 7: Test</H2><PRE><CODE>curl https://bp-function.<your-cluster-domain>.kyma.ondemand.com</CODE></PRE><P>You should see Business Partner records:</P><PRE><CODE>{
"d": {
"results": [
{
"BusinessPartner": "11",
"BusinessPartnerFullName": "Cust15 Cust15",
...
}
]
}
}</CODE></PRE><HR /><H2 id="toc-hId-199443253">How It Works</H2><P>The call chain is the same as the original Kyma Function approach:</P><PRE><CODE>Internet -> APIRule (Istio) -> Service -> Pod
-> XSUAA (get OAuth token)
-> Destination Service (resolve S4H_SANDBOX_BP)
-> SAP API Hub sandbox (fetch Business Partners)
-> JSON response</CODE></PRE><P>The difference is that your code runs in a container you built and pushed, rather than in the Kyma serverless runtime. The BTP Service Operator still injects the Destination Service credentials into the pod as environment variables via the ServiceBinding secret ā that part is identical.</P><HR /><H2 id="toc-hId-2929748">Key Points</H2><P><STRONG>The Serverless module is optional.</STRONG> The BTP Service Operator, APIRule, and Istio are the parts that matter for this integration pattern. If Serverless isn't enabled, a standard Deployment works just as well.</P><P><STRONG>Instance destinations vs subaccount destinations.</STRONG> If you can't create destinations in the BTP cockpit, use <CODE>POST /destination-configuration/v1/instanceDestinations</CODE>. These are scoped to the service instance and can be created using the binding credentials. They're resolved at runtime just like subaccount destinations.</P><P><STRONG>Multi-platform builds matter.</STRONG> Apple Silicon Macs build <CODE>linux/arm64</CODE> by default. Use <CODE>docker buildx build --platform linux/amd64,linux/arm64</CODE> to produce a manifest list that works on both architectures ā avoiding the cryptic <CODE>no match for platform in manifest</CODE> error.</P><P><STRONG>Use the full APIRule hostname.</STRONG> In Kyma API Gateway v2, short hostnames in the <CODE>hosts</CODE> field expand to include the namespace (<CODE>bp-function.dev-space-neil.<cluster-domain></CODE>). This may not match your cluster's wildcard DNS. Use the full explicit hostname (<CODE>bp-function.<cluster-domain></CODE>) to be safe ā check what format existing APIRules in your cluster use.</P><P><STRONG>Gzip is not optional.</STRONG> The SAP API Business Hub sandbox always returns gzip-compressed responses regardless of what you put in <CODE>Accept-Encoding</CODE>. Handle it explicitly with Node's built-in <CODE>zlib</CODE> module.</P>2026-06-10T22:01:00.221000+02:00https://community.sap.com/t5/sap-for-utilities-blog-posts/atul-architecture-4-ai-on-sap-btp-for-grid-operations/ba-p/14419316ATUL Architecture #4 ā AI on SAP BTP for Grid Operations2026-06-17T11:21:39.612000+02:00Atul_Joshi85https://community.sap.com/t5/user/viewprofilepage/user-id/2274193<H1 id="toc-hId-1688354592">ATUL Architecture #4 ā AI on SAP BTP for Grid Operations</H1><P><STRONG><EM>How AI Becomes Actionable Inside SAP Processes</EM></STRONG></P><H3 id="toc-hId-1750006525"><STRONG>1. </STRONG><STRONG>Introduction</STRONG></H3><P>Utilities generate massive volumes of operational data every secondāfrom SCADA systems, AMI networks, intelligent electronic devices (IEDs), weather feeds, and grid sensors. While collecting data is no longer the challenge, transforming that data into actionable operational decisions remains difficult. Most AI initiatives stop at dashboards and predictions. However, real business value is realized only when AI insights automatically trigger operational processes inside SAP.</P><P>In this architecture, I demonstrate how SAP BTP combines time-series data, machine learning, event-driven integration, and SAP S/4HANA workflows to create a closed-loop operational model where AI does not simply predict problemsāit initiates action.</P><P>The goal is simple: <STRONG>AI should not just predict. AI should trigger work.</STRONG></P><P>Most utilities already have advanced analytics and dashboards. The real challenge is operationalizing AIāmoving from insight to action.<BR />This architecture demonstrates how SAP BTP closes that gap by embedding AI directly into operational workflows, ensuring that predictions automatically trigger governed actions inside SAP S/4HANA.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Atul_Joshi85_0-1781565307119.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/422020i0B52F0BC396DF4BD/image-size/medium?v=v2&px=400" role="button" title="Atul_Joshi85_0-1781565307119.png" alt="Atul_Joshi85_0-1781565307119.png" /></span></P><H3 id="toc-hId-1553493020"><STRONG>2. </STRONG><STRONG>Foundation: Timeseries + AI on BTP</STRONG></H3><P><STRONG>TimeāSeries Data Foundation: Grid operations run on timeāseries data. Typical examples include:</STRONG></P><UL><LI>Transformer temperature curves</LI><LI>Voltage fluctuations</LI><LI>Feeder imbalance</LI><LI>Breaker operation frequency</LI><LI>Meter communication patterns</LI><LI>Weather impact signals</LI></UL><P>SAP BTP provides two strong foundations for this:</P><UL><LI>SAP HANA Cloud ā native timeāseries functions, fast analytics</LI><LI>SAP Datasphere ā semantic modeling + integration</LI></UL><P>Most utilities already have this data in SCADA, OMS, or AMI systems. The architecture standardizes and contextualizes this data within SAP BTP, ensuring it is immediately consumable by AI models and downstream processes.</P><P><STRONG>Anomaly Detection on BTP</STRONG></P><P>Once timeāseries data is available, the next step is anomaly detection. Common utility scenarios:</P><UL><LI>Sudden voltage drop on a feeder</LI><LI>Transformer overheating pattern</LI><LI>Meter not reporting for 48 hours</LI><LI>Repeated breaker operations</LI><LI>Unusual load spike</LI></UL><P>SAP AI Core + AI Launchpad support models such as:</P><UL><LI>LSTMābased anomaly detection</LI><LI>Isolation Forest</LI><LI>Autoencoders</LI><LI>Thresholdābased rules for simple cases</LI></UL><P>The output is always the same: āSomething looks wrong here.ā But anomaly detection alone does not help operations. The value comes when the anomaly creates an action. In grid operations, the value of anomaly detection lies not in identifying every deviation, but in identifying actionable deviationsāthose that require operational response within defined time windows.</P><H3 id="toc-hId-1356979515"><STRONG>3. </STRONG><STRONG>Predictive Maintenance for Grid Assets</STRONG></H3><P>Predictive maintenance models help utilities answer:</P><UL><LI>When will this transformer fail</LI><LI>Which feeder is at highest risk next week</LI><LI>Which assets need inspection before summer load</LI></UL><P>SAP BTP supports this through:</P><UL><LI>AI Core for model training</LI><LI>HANA Cloud for feature engineering</LI><LI>Event Mesh for eventādriven triggers</LI><LI>SAP Build Process Automation for workflows</LI></UL><P>The model output becomes a risk score or failure probability. But prediction alone is not enough. It must flow into SAP S/4HANA. From a business perspective, predictive maintenance shifts operations from reactive repair to risk-based intervention, allowing utilities to prioritize maintenance budgets toward high-impact assets and reduce unplanned outage exposure.</P><H3 id="toc-hId-1160466010"><STRONG>4. </STRONG><STRONG>AI Workflow S/4HANA Integration (The "Gatekeeper" Layer)</STRONG></H3><P>This is where the architecture ensures both speed and safety. AI becomes valuable only when it creates work, but critical infrastructure requires accountability.</P><P>Integrating Manual Approvals: Before the automated creation of a work order in S/4HANA, insert an Approval Step within SAP Build Process Automation.</P><P>This āgatekeeper layerā is essential in regulated industries. Fully autonomous AI decisions are rarely acceptable in utility operations. By inserting human approval only when risk thresholds are exceeded, the architecture balances automation with control. This ensures safety, regulatory compliance, and operator trust, while still preserving the speed advantages of AI-driven decisioning.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Atul_Joshi85_1-1781565307158.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/422021iD67290C6B52BC08B/image-size/medium?v=v2&px=400" role="button" title="Atul_Joshi85_1-1781565307158.png" alt="Atul_Joshi85_1-1781565307158.png" /></span></P><UL><LI>The Pattern: When the AI model reaches a "low-confidence" threshold or identifies a high-risk asset, the workflow automatically pauses.</LI><LI>The Interface: A task is pushed to the supervisor's SAP Build Inbox, displaying the AIās risk score, supporting technical data, and a "Approve/Reject" button.</LI><LI>The Result: The S/4HANA work order is only generated after human verification, ensuring that the system is an "AI-assisted" tool rather than a fully autonomous one.</LI><LI>AI ā Workflow ā S/4HANA Integration</LI></UL><P>This is the most important part of architecture. AI becomes valuable only when it creates work.</P><P><STRONG>Example Flow</STRONG></P><OL><LI>AI model detects transformer overheating</LI><LI>Event Mesh publishes an event</LI><LI>SAP Build Process Automation starts a workflow</LI><LI>Workflow checks asset master data in S/4HANA</LI><LI>A maintenance notification or work order is created</LI><LI>Field technician receives the job in SAP FSM or mobile app</LI></OL><P>This is where utilities see real impact:</P><OL><LI>Faster response</LI><LI>Fewer outages</LI><LI>Better asset life</LI><LI>Lower O&M cost</LI></OL><P>AI is not a dashboard. AI is a <STRONG>trigger</STRONG>.</P><H3 id="toc-hId-963952505"><STRONG>5. </STRONG><STRONG>Real Utility Scenarios</STRONG></H3><P>Scenario 1 ā Feeder Overload Prediction</P><OL><LI>AI predicts overload 48 hours ahead</LI><LI>Workflow creates a loadābalancing task</LI><LI>S/4HANA logs the action for audit</LI></OL><P>Scenario 2 ā Transformer Failure Risk</P><OL><LI>Model detects abnormal temperature pattern</LI><LI>Work order created automatically</LI><LI>Technician dispatched before failure</LI></OL><P>Scenario 3 ā Meter Communication Failure</P><OL><LI>AI detects repeated nonāreporting meters</LI><LI>Workflow assigns investigation to AMI team</LI><LI>S/4HANA tracks resolution</LI></OL><P>These are simple, but they save real money. Across utilities, such scenarios have the potential to significantly reduce mean time to detection (MTTD), minimize outage duration, and improve asset utilization efficiency.</P><H3 id="toc-hId-767439000">6. <STRONG>Architecture</STRONG> View 1: AIāTriggered Transformer Overheating Workflow (EndātoāEnd)</H3><P><STRONG>Flow Explanation</STRONG></P><OL><LI>SCADA / IoT Gateway Streams transformer temperature every 5 seconds.</LI><LI>SAP BTP ā HANA Cloud Stores timeseries + applies smoothing, rolling averages, and feature extraction.</LI><LI>SAP AI Core LSTM model detects abnormal temperature rise ā generates anomaly score.</LI><LI>SAP Event Mesh Publishes event: transformer->overheat->detected.</LI><LI>SAP Build Process Automation<UL><LI>Reads asset master from S/4</LI><LI>Apply business rules (risk threshold, asset criticality)</LI><LI>Sends approval task to supervisor</LI></UL></LI><LI>SAP S/4HANA PM<UL><LI>Creates Maintenance Notification</LI><LI>Converts to Work Order after approval</LI></UL></LI><LI>SAP Field Service Management Dispatches technician with asset details + AI explanation.</LI></OL><H3 id="toc-hId-570925495"><STRONG>7. </STRONG><STRONG>Architecture</STRONG><STRONG> View 2: AMI Meter NonāReporting Investigation Workflow</STRONG></H3><P><STRONG>Meter Communication Failure Flow ā A simple but highāvalue scenario.</STRONG></P><P><STRONG>Flow Explanation</STRONG></P><OL><LI>AMI Headend System Detects meter not reporting for 48 hours.</LI><LI>SAP BTP ā Datasphere Harmonizes meter metadata, location, customer info.</LI><LI>SAP AI Core Isolation Forest model identifies ācommunication anomaly cluster.ā</LI><LI>SAP Event Mesh Publishes event: meter->communication->anomaly.</LI><LI>SAP Build Process Automation</LI><LI>Checks if meter is in outage area</LI><LI>Checks last service history</LI><LI>Assigns task to AMI Ops Team</LI><LI>SAP S/4HANA Customer Service / PM Creates service order or investigation ticket.</LI><LI>Technician Mobile App Receives job with meter location + AI reasoning.</LI></OL><P><STRONG> </STRONG><STRONG>8. </STRONG><STRONG>GroundāReality Example: āThe Black Friday Surge Eventā</STRONG></P><P>Black Fridayāstyle load spikes happen in utilities too ā sudden, unpredictable, and dangerous. Hereās the short version you can paste into your paper:</P><P>āDuring a sudden preāholiday coldāweather surge, Feeder Fā19ās load jumped 14% in under 3 minutes. SAP HANA Cloud flagged the abnormal ramp, SAP AI Core predicted an overload within 45 minutes, and Event Mesh triggered an automated workflow. A supervisor approved a preāemptive load transfer, and S/4HANA<STRONG> executed the </STRONG>switching plan. The feeder stabilized within 20 minutes ā preventing an outage during peak demand.ā</P><H3 id="toc-hId-374411990"><STRONG>9. </STRONG><STRONG>Key Architectural Principles</STRONG></H3><P><STRONG>Point 1 āAI runs on BTP, close to SAP data" or AI Proximity to SAP Data Matters</STRONG></P><OL><LI>Right now, it just says "No complex integration." That's a feature, not an insight. What you should explain is why proximity matters architecturally ā when AI models live in the same platform as your master data, you eliminate the ETL lag, data translation errors, and security surface area that come with external AI platforms. The practical implication: your anomaly detection is working on the same asset hierarchy that S/4HANA uses to create work orders, so there's no mismatch between "what the AI saw" and "what the technician gets dispatched to fix." That's a real architectural win worth spelling out.</LI></OL><P><STRONG>Point 2 āEvent-Driven Architecture Reduces Operational Latency"</STRONG></P><OL><LI>"AI triggers work instantly" is too vague. The deeper idea here is about operational latency ā in grid operations, the window between early warning and failure can be hours or even minutes. A polling-based batch integration would miss that window entirely. Event Mesh ensures the moment AI flags an anomaly; the downstream workflow fires. You could contrast this with the old model: AI generates a report overnight, an analyst reviews it in the morning, creates a ticket manually ā by which time the transformer has already tripped. Event-driven isn't just faster; it changes the category of what's possible.</LI></OL><P><STRONG>Point 3 "S/4HANA becomes the system of action"</STRONG></P><OL><LI>The current line misses the real point, which is about organizational trust and auditability. Utilities are regulated industries. Work orders, notifications, and approvals don't just need to happen ā they need to be traceable, auditable, and defensible to regulators and internal compliance teams. Because S/4HANA is already the system of record for asset management and maintenance, routing AI-triggered actions through it means you don't need a parallel tracking system. The AI's recommendation and the resulting field action live in the same audit trail. That's not a convenience ā it's a compliance requirement for most utilities.</LI></OL><P><STRONG>Point 4 āMeasurable Business Outcomes Define Success"</STRONG></P><OL><LI>"Fewer outages, faster response, better asset health" reads like a brochure. The improvement here is to be specific about how you'd measure these. For example: reduction in mean time to detect (MTTD) on grid anomalies, reduction in unplanned outage minutes per feeder, or improvement in asset utilization rates as preventive maintenance replaces reactive repair. Even if you don't have exact numbers, framing the metrics gives readers a way to build a business case internally ā which is often exactly what a utility architect needs when presenting this to leadership.</LI></OL><H3 id="toc-hId-177898485"><STRONG>10. </STRONG><STRONG>Governance Considerations</STRONG></H3><UL><LI>AI model versioning and monitoring using SAP AI Core</LI><LI>Data lineage and semantic consistency via Datasphere</LI><LI>Secure integration through SAP BTP service layer</LI><LI>Role-based access for approval workflows</LI></UL><P>These elements ensure that AI-driven automation remains compliant, auditable, and enterprise-ready.</P><H3 id="toc-hId--93846389"><STRONG>11. </STRONG><STRONG>Final Thoughts</STRONG></H3><P>AI for grid operations is not about fancy models. Itās about connecting predictions to SAP processes.</P><P>Timeāseries data ā AI model ā Event ā Workflow ā S/4HANA action.</P><P>The future of utility operations is not autonomous AI replacing operators. It is AI-driven workflows that help operators act earlier, with better information, inside trusted SAP business processes. When predictions automatically become notifications, approvals, work orders, and field actions, SAP BTP becomes the operational backbone of grid modernization.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Atul_Joshi85_2-1781565307200.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/422022i5F5B2B7142F78B97/image-size/medium?v=v2&px=400" role="button" title="Atul_Joshi85_2-1781565307200.png" alt="Atul_Joshi85_2-1781565307200.png" /></span></P><H3 id="toc-hId--290359894"><STRONG>12. </STRONG><STRONG>Practical Architecture Example: āThe 3 AM Saveā</STRONG></H3><P><STRONG>StepābyāStep Flow (Real Utility Reality)</STRONG></P><OL><LI>2:58 AM ā SCADA temperature spike Transformer Tā221 shows a sudden 9°C rise in 4 minutes.</LI><LI>3:00 AM ā SAP HANA Cloud detects abnormal curve Rolling window + derivative features show a deviation from normal nighttime cooling.</LI><LI>3:01 AM ā SAP AI Core LSTM flags anomaly Risk score jumps from 0.18 ā 0.74. Not a failure yet ā but trending toward one.</LI><LI>3:01:10 AM ā SAP Event Mesh publishes event grid->transformer->overheat->predicted.</LI><LI>3:01:12 AM ā SAP Build Process Automation starts workflow</LI><UL><LI>Pulls asset master from S/4</LI><LI>Checks transformer age (17 years)</LI><LI>Checks last maintenance (14 months ago)</LI><LI>Checks load forecast (high morning peak expected)</LI></UL><LI>3:01:30 AM ā Supervisor approval triggered A mobile notification appears: <EM>āAI predicts overheating in 3ā5 hours. Approve preāfailure inspection?ā</EM></LI><LI>3:03 AM ā Supervisor taps āApproveā (This is the humanāinātheāloop safety layer.)</LI><LI>3:03:10 AM ā SAP S/4HANA creates work order Priority: High Task: Inspect cooling fins + oil level + load tap changer</LI><LI>3:20 AM ā Field technician receives job on mobile Drives to site before failure window.</LI><LI>4:05 AM ā Technician finds blocked cooling fins Dust + debris + vegetation restricting airflow.</LI><LI>4:40 AM ā Issue cleared, temperature stabilizes Transformer returns to normal operating range.</LI><LI>Failure avoided. Outage avoided. $40k avoided. And the entire chain is auditable inside S/4HANA.</LI></OL><P><STRONG>What Example Proves (The Architectural Insight)</STRONG></P><P>This single incident captures the entire purpose of architecture. The utility didnāt just detect a problem ā it prevented failure because AI, events, workflows, and S/4HANA acted as one system. The model didnāt need to be perfect; it only needed to be early. Event Mesh eliminated latency. Build Process Automation added human judgment. S/4HANA ensured the action was auditable and compliant. And the field technician received the job before the asset crossed the failure threshold.</P><P>This is the difference between āAI that predictsā and AI that protects the grid.</P><P> </P><P> </P>2026-06-17T11:21:39.612000+02:00https://community.sap.com/t5/sap-for-utilities-blog-posts/atul-architecture-5-designing-a-utility-data-fabric-on-sap-btp/ba-p/14433110ATUL Architecture #5 ā Designing a Utility Data Fabric on SAP BTP2026-07-03T17:50:48.910000+02:00Atul_Joshi85https://community.sap.com/t5/user/viewprofilepage/user-id/2274193<H1 id="toc-hId-1690020960">ATUL Architecture #5 ā Designing a Utility Data Fabric on SAP BTP</H1><P class="lia-align-center" style="text-align: center;"><STRONG><EM>How Utilities Build a Unified, Governed, RealāTime Data Layer for Grid + Customer Operations</EM></STRONG></P><H3 id="toc-hId-1751672893"><STRONG>1. </STRONG><STRONG>Introduction </STRONG></H3><P>For SAP-centric utilities, the challenge is no longer simply managing growing volumes of data. Utilities have invested heavily in SAP IS-U, SAP S/4HANA, AMI platforms, SCADA systems, GIS applications, outage management solutions, and a growing ecosystem of grid technologies. Yet much of this information remains trapped within operational silos, making it difficult to create a unified view of grid and customer operations.</P><P><STRONG>The real problem is no longer collecting data, but connecting, governing, and using it across the enterprise.</STRONG></P><P>As utilities navigate the rise of distributed energy resources (DERs), electric vehicles (EVs), advanced metering infrastructure (AMI), and increasingly dynamic grid operations, traditional batch-oriented architectures struggle to keep pace. The grid is becoming real time, but the data architecture often remains fragmented.</P><P>A Business Data Fabric built on SAP Datasphere and SAP HANA Cloud provides a modern architectural approach for unifying SAP and non-SAP data without discarding existing investments. By combining data integration, semantic modeling, governance, and real-time access capabilities, utilities can create a trusted enterprise data layer that supports analytics, AI, operational intelligence, and regulatory reporting from a common foundation.</P><P>This is where Utility Data Fabric becomes essentialāand where SAP BTP provides the platform to make it practical.</P><P>Most utilities still operate with:</P><UL><LI>Siloed operational systems</LI><LI>Fragmented data marts</LI><LI>Batchādriven ETL pipelines</LI><LI>Inconsistent semantic definitions</LI><LI>Limited realātime visibility</LI></UL><P>This creates a fundamental gap: <STRONG>The grid is realātime, but the data architecture is not.</STRONG></P><P>This is where Utility<STRONG> Data Fabric</STRONG> becomes essential ā and SAP BTP provides the platform to build it.</P><H3 id="toc-hId-1555159388"><STRONG>2. </STRONG><STRONG>A Utility Data Fabric in Action: Transformer Outage Scenario</STRONG></H3><P>To understand how Utility Data Fabric works, consider a common operational event transformer failure affecting several hundred customers.</P><P>A SCADA system detects abnormal voltage conditions and reports the event in real time. AMI smart meters simultaneously stop communicating, confirming the outage footprint. GIS identifies the affected transformer, feeder, and service territory, while the Outage Management System (OMS) creates an outage event and initiates restoration activities.</P><P>Within the Utility Data Fabric, these events are combined with asset history, maintenance records, weather conditions, and customer information to create a unified operational view. Customer notification systems can automatically communicate outage information, analytics platforms can assess business impact, and AI models can predict restoration timelines based on historical outage patterns.</P><P>This single event touches nearly every layer of the Data Fabricāfrom data ingestion and semantic modeling to governance, analytics, and AI-driven decision support.</P><H3 id="toc-hId-1358645883"><STRONG>3. </STRONG><STRONG>What Utility Data Fabric Actually Means</STRONG></H3><P>Data Fabric is not a single product. It is an <STRONG>architectural approach</STRONG> that unifies:</P><UL><LI>Data ingestion</LI><LI>Data storage</LI><LI>Semantic modeling</LI><LI>Governance</LI><LI>Access control</LI><LI>Analytics</LI><LI>AI/ML consumption</LI></UL><P>Across <STRONG>all</STRONG> utility domains:</P><UL><LI>Grid operations</LI><LI>Customer operations</LI><LI>Metering</LI><LI>Billing</LI><LI>Asset management</LI><LI>Field operations</LI><LI>Regulatory reporting</LI></UL><P>In simple terms: <STRONG>A Data Fabric makes data findable, usable, trusted, and realātime ā across the entire utility.</STRONG></P><P>SAP BTP provides the building blocks to make this practical.</P><H3 id="toc-hId-1162132378"><STRONG>4. </STRONG><STRONG>The Core Components of a Utility Data Fabric on SAP BTP</STRONG></H3><H4 id="toc-hId-1094701592"><STRONG>Data Lake Layer</STRONG></H4><P>This is the foundation. SAP HANA Cloud provides the underlying multi-model data platform for storing and processing enterprise data, while SAP Datasphere delivers the semantic, integration, and governance capabilities that form the Business Data Fabric layer. A key principle of SAP's Business Data Fabric approach is that not all data must be physically moved into a central repository. SAP Datasphere supports both replication and federation techniques, allowing utilities to access and combine data across SAP and non-SAP systems while minimizing unnecessary data duplication. This approach helps reduce integration costs, improve data freshness, and preserve investments in existing operational platforms. For large historical datasets such as AMI interval reads and telemetry archives, SAP HANA Cloud Data Lake can provide lower-cost storage while maintaining integration with Datasphere and HANA Cloud analytics services. In the transformer outage scenario, telemetry from SCADA, outage events from OMS, AMI meter status, GIS network topology, and customer information are ingested and made available through the Data Fabric for unified analysis</P><P>The Data Fabric approach allows utilities to federate data across hybrid landscapesākeeping legacy data in on-premises systems while virtualizing access in the cloudāreducing the latency and cost of moving massive, static datasets.</P><P>Utilities ingest:</P><UL><LI>AMI meter events</LI></UL><UL><LI>SCADA/IoT streams</LI><LI>DER telemetry</LI><LI>Weather + Wildfire Risk Data</LI><LI>Customer interactions</LI><LI>Billing determinants</LI><LI>Asset health and maintenance history</LI><LI>Outage management events (OMS)</LI></UL><P>For utilities, geospatial information is as important as transactional data. Modern Data Fabrics must integrate GIS systems, network topology models, feeder maps, outage polygons, vegetation management zones, and wildfire risk areas. By combining spatial context with customer, asset, and operational data, utilities can perform advanced use cases such as outage impact analysis, restoration prioritization, vegetation risk assessment, DER hosting capacity planning, and climate resilience modeling. SAP Datasphere can combine this geospatial context with operational and customer data to create business-ready models for planning, analytics, and AI use cases.</P><UL><LI>The data lake must support:</LI></UL><UL><LI>Highāvolume ingestion</LI><LI>Lowācost storage</LI><LI>Realātime streaming</LI><LI>Timeāseries analytics</LI><LI>Geospatial analysis</LI></UL><P>This is where <STRONG>grid + customer data finally live together</STRONG>.</P><P>A<STRONG>nalytical Model</STRONG> and <STRONG>Consumption Model</STRONG> concepts in Datasphere. In SAP Datasphere, analytical models define measures and semantics, while consumption models expose businessāready views for analytics and AI. The goal is not to move every dataset into one database. The goal is to provide one trusted view of enterprise data regardless of where it physically resides</P><H4 id="toc-hId-898188087"><STRONG>Semantic Models</STRONG></H4><P>This is the most misunderstood part of a Data Fabric.</P><P>Semantic models define:</P><UL><LI>What is a meter?</LI><LI>What is an outage?</LI><LI>What is a transformer?</LI><LI>What is billing determinant?</LI><LI>What is a customer event?</LI></UL><P>Without semantic consistency:</P><UL><LI>Analytics break</LI><LI>AI models fail</LI><LI>Reports contradict each other</LI><LI>Regulatory submissions become risky</LI></UL><P>A Business Data Fabric combines data integration, governance, semantic modeling, and real-time access into a single enterprise architecture without requiring all data to be physically centralized. SAP Datasphereās <STRONG>Business Data Fabric</STRONG> capabilities allow utilities to create:</P><UL><LI>Harmonized data models</LI><LI>Domaināspecific views</LI><LI>Reusable data products</LI><LI>Crossādomain relationships.</LI></UL><P>Rather than exposing raw tables or application-specific structures, utilities can publish governed data products that package data with business meaning, ownership, quality standards, and access controls. Examples include a customer 360 product, Asset Health product, Outage Operations product, DER Portfolio product, or Transformer Risk product. These reusable data products become the primary mechanism through which analytics teams, AI solutions, operational applications, and business users consume trusted enterprise data. For example, during a transformer outage, different systems may refer to the same asset using different identifiers. Semantic models help establish a consistent definition of transformers, feeders, outages, and affected customers across all participating systems.</P><H3 id="toc-hId-572591863"><STRONG>Master Data Foundation</STRONG></H3><P>Semantic consistency begins with trusted master data. Utilities often maintain customer, asset, feeder, transformer, meter, and service point information across multiple systems, including SAP S/4HANA, SAP IS-U, GIS platforms, outage management systems, and third-party applications.</P><P>Utility Data Fabric must establish a consistent master data foundation so that the same business object is represented identically across the enterprise. Without master data harmonization, analytics become unreliable, AI models generate inconsistent results, and cross-domain reporting loses credibility.</P><P>By combining semantic models with governed master data, utilities create a trusted foundation for enterprise-wide analytics, operational intelligence, and regulatory reporting. SAP Master Data Governance (MDG) can further support this foundation by governing critical utility master data domains and synchronizing trust records across enterprise applications.</P><H4 id="toc-hId-505161077"><STRONG>Governance Layer</STRONG></H4><P><STRONG>Governance is not documentation. It is control + trust</STRONG>: Utilities must define</P><UL><LI>Data ownership (domainādriven)</LI><LI>Data quality rules</LI><LI>Lineage and traceability</LI><LI>Access policies</LI><LI>Retention and compliance</LI><LI>Security and privacy</LI></UL><P>SAP BTP provides:</P><UL><LI>Central governance via Datasphere</LI><LI>Cataloging and lineage</LI><LI>Roleābased access</LI><LI>Policy enforcement</LI><LI>Auditability</LI></UL><P>Governance is what makes Data Fabric <EM>safe</EM>. Utility Data Fabrics must also address cybersecurity and regulatory requirements. As operational technology (OT) data from AMI, SCADA, and grid systems becomes integrated with enterprise IT platforms, utilities must enforce appropriate segmentation, access controls, auditability, and compliance requirements such as NERC CIP where applicable. During an outage investigation, data lineage and auditability help utilities understand where information originated, how it was transformed, and whether operational decisions were based on trusted data sources.</P><P><STRONG>Active Metadata: The Intelligence Behind the Data Fabric</STRONG></P><P>A modern Data Fabric does not operate solely on data; it also depends on metadata that describes how data is created, transformed, consumed, and governed. Active metadata captures information such as data lineage, ownership, quality scores, usage patterns, business definitions, and relationships between datasets.</P><P>Within SAP Datasphere, metadata helps utilities understand where data originated, who owns it, how it is being used, and whether it meets governance standards. As utility data landscapes continue to grow, active metadata becomes essential for automated impact analysis, policy enforcement, data discovery, and trusted AI consumption.</P><P>In many successful Data Fabric implementations, metadata acts as the operational intelligence layer that continuously monitors and improves the health of the overall data ecosystem.</P><P><STRONG>Analytics for Grid + Customer</STRONG></P><P>This is where Data Fabric becomes valuable. Utilities can build:</P><P><STRONG>Grid Analytics</STRONG></P><UL><LI>Outage prediction</LI><LI>Load forecasting</LI><LI>DER hosting capacity</LI><LI>Feederālevel performance</LI><LI>Voltage optimization</LI><LI>Asset failure prediction</LI></UL><P><STRONG>Customer Analytics</STRONG></P><UL><LI>Highābill alerts</LI><LI>Usage pattern segmentation</LI><LI>EV charging behavior</LI><LI>Payment risk scoring</LI><LI>Realātime notifications</LI><LI>Personalized rate recommendations</LI></UL><P><STRONG>Enterprise Analytics</STRONG></P><UL><LI>Regulatory reporting</LI><LI>Operational KPIs</LI><LI>Financial performance</LI><LI>Workforce optimization</LI></UL><P>SAP Analytics Cloud + Datasphere + HANA Cloud provide a unified analytics stack.</P><P><STRONG>AI Readiness Through Data Fabric </STRONG></P><P>The long-term value of a Utility Data Fabric extends beyond reporting and analytics. It provides the trusted data foundation required for enterprise AI, machine learning, and autonomous decision-making. AI models perform best when they are trained and operated on governed, semantically consistent, and high-quality data.</P><P>By combining semantic models, master data harmonization, governance controls, and lineage capabilities, a Utility Data Fabric reduces the risk of inaccurate predictions, inconsistent recommendations, and model drift. This foundation becomes critical for initiatives such as outage prediction, asset failure forecasting, energy demand optimization, and intelligent customer engagement. Examples include outage prediction, transformer failure forecasting, DER optimization, and proactive customer notification models. In the transformer outage example, AI models can combine outage history, asset condition, weather data, and crew availability to improve restoration estimates and recommend remediation actions.</P><P><STRONG>BTP Integration: </STRONG></P><P>This architecture doesn't exist in a vacuum. It integrates directly with <STRONG>SAP Build Process Automation</STRONG> (for triggering workflows from data insights) and <STRONG>SAP AI Core</STRONG> (for deploying models trained on Datasphere data products). SAP Integration Suite plays a critical role in Data Fabric architecture by connecting SAP IS-U, SAP S/4HANA, SAP EAM, GIS systems, outage management platforms, and external data providers. Through Cloud Integration, API Management, and Event Mesh, Integration Suite enables secure, governed, and scalable movement of data across hybrid utility landscapes.</P><H3 id="toc-hId-179564853"><STRONG>How the Data Fabric Works EndātoāEnd </STRONG></H3><P><STRONG>Step 1 ā Ingest</STRONG></P><UL><LI>AMI events via Event Mesh</LI><LI>IoT/SCADA via MQTT</LI><LI>Customer interactions via APIs</LI><LI>Asset data via S/4HANA</LI></UL><P>Traditional data architectures rely on batch-based movement of information between systems. Modern utility operations require a different approach. Smart meters, distributed energy resources, grid sensors, and customer engagement platforms continuously generate events that must be processed in near real time. SAP Event Mesh enables these events to be distributed across operational systems and data platforms, where they can be consumed by analytics, automation services, and AI models. creating a continuously updated Data Fabric rather than a periodically refreshed repository.</P><P><STRONG>Step 2 ā Store</STRONG></P><UL><LI>Raw zone (immutable)</LI><LI>Refined zone (cleaned)</LI><LI>Curated zone (businessāready)</LI></UL><P><STRONG>Step 3 ā Model</STRONG></P><UL><LI>Semantic definitions</LI><LI>Domain views</LI><LI>Data products</LI></UL><P><STRONG>Step 4 ā Govern</STRONG></P><UL><LI>Quality rules</LI><LI>Lineage</LI><LI>Access control</LI></UL><P><STRONG>Step 5 ā Consume</STRONG></P><UL><LI>Grid operations</LI><LI>Customer operations</LI><LI>Field mobility</LI><LI>AI/ML models</LI><LI>Analytics dashboards</LI></UL><P>This is the <STRONG>Utility Data Fabric lifecycle</STRONG>. The Data Fabric helps align gridālevel latency requirements (millisecond responses for protection) with enterpriseālevel latency (near realātime analytics), ensuring the data architecture does not become a bottleneck for critical operations.</P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Atul_Joshi85_0-1783092438470.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429028i7357EC9537799ED6/image-size/medium?v=v2&px=400" role="button" title="Atul_Joshi85_0-1783092438470.png" alt="Atul_Joshi85_0-1783092438470.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Atul_Joshi85_1-1783092438505.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429026i9C20A35A2804DA29/image-size/medium?v=v2&px=400" role="button" title="Atul_Joshi85_1-1783092438505.png" alt="Atul_Joshi85_1-1783092438505.png" /></span></P><P><span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Atul_Joshi85_2-1783092438543.png" style="width: 400px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/429029i015689E3587D1290/image-size/medium?v=v2&px=400" role="button" title="Atul_Joshi85_2-1783092438543.png" alt="Atul_Joshi85_2-1783092438543.png" /></span></P><P>Returning to our transformer outage example, the same operational event moves through every stage of the Data Fabric lifecycleāfrom ingestion and storage through semantic modeling, governance, analytics, and AI-driven decision support.</P><H3 id="toc-hId--92180021"><STRONG>5. </STRONG><STRONG> </STRONG><STRONG>Why Utilities Need Data Fabric Now</STRONG></H3><UL><LI><STRONG>Because the grid is becoming realātime: </STRONG>DERs, EVs, and AMI require continuous intelligence.</LI><LI><STRONG>Because AI depends on clean, governed data: </STRONG>AI is useless without trusted data.</LI><LI><STRONG>Because regulators expect transparency: </STRONG>Lineage, auditability, and data quality are no longer optional.</LI><LI><STRONG>Because customer expectations have changed: </STRONG>Realātime notifications, usage insights, and personalized experiences require unified data<STRONG>.</STRONG></LI><LI><STRONG>Because operational silos are too expensive: </STRONG>A Data Fabric reduces duplication, integration cost, and data chaos.</LI></UL><H3 id="toc-hId--288693526"><STRONG>6. </STRONG><STRONG>Data Fabric vs Traditional Utility Data Warehouse</STRONG></H3><P>Many utilities already operate enterprise data warehouses and may question why a Data Fabric is needed. Traditional data warehouses typically rely on periodic ETL processes that copy data into centralized repositories for reporting purposes. While effective for historical analytics, they often struggle to support real-time operational use cases, cross-domain intelligence, and AI-driven decision making.</P><P>Utility Data Fabric complements and extends the traditional warehouse by introducing semantic consistency, federated data access, active governance, reusable data products, and near real-time integration. Rather than simply storing data, the Data Fabric creates a governed and intelligent data layer that connects operational systems, analytical platforms, and AI services across the enterprise.</P><P>In simple terms, a traditional warehouse answers questions about the past, while a Data Fabric enables utilities to act on what is happening now.</P><P>Traditional Data Warehouse</P><UL><LI>Periodic ETL</LI><LI>Historical reporting</LI><LI>Centralized storage</LI><LI>Primarily analytics focused</LI></UL><P>Data Fabric</P><UL><LI>Real-time data access</LI><LI>Federation and virtualization</LI><LI>Semantic consistency</LI><LI>Data products</LI><LI>AI consumption</LI><LI>Cross-domain intelligence</LI></UL><H3 id="toc-hId--485207031"><STRONG>7. </STRONG><STRONG>Common Pitfalls (What Fails in Real Projects): </STRONG></H3><P>Pitfall: Attempting to replicate all data instead of federating it. Leverage Datasphereās ability to virtualize data from SAP S/4HANA and external sources to reduce ETL overhead and latency.</P><UL><LI><STRONG>Treating the Data Fabric as an IT project: </STRONG>It must be businessāowned and domainādriven.</LI><LI><STRONG>Overloading the data lake with unstructured noise: </STRONG>Raw data without governance becomes a ādata swamp.ā</LI><LI><STRONG>No semantic consistency: </STRONG>Different teams define the same metric differently.</LI><LI><STRONG>Ignoring realātime ingestion: </STRONG>Batch pipelines cannot support modern grid operations.</LI><LI><STRONG>Not designing data products: </STRONG> Data must be reusable, not oneāoff.</LI></UL><H4 id="toc-hId--975123543"><STRONG>The Architecture Principle That Matters Most</STRONG></H4><P>Data Fabric is not a database. It is a governed operating model for how utility data is shared, trusted, and consumed across the enterprise.</P><P>SAP BTP provides the platform. The utility provides discipline.</P><H3 id="toc-hId--878234041"><STRONG>What Success Looks Like</STRONG></H3><P>A successful Utility Data Fabric delivers:</P><UL><LI>A single source of truth</LI><LI>Realātime grid + customer visibility</LI><LI>Faster regulatory reporting</LI><LI>Higher data quality</LI><LI>Better AI outcomes</LI><LI>Lower integration cost</LI><LI>Stronger cybersecurity posture</LI><LI>Improved customer experience</LI></UL><P>This is the foundation for the <STRONG>AIāenabled utility</STRONG>.</P><H3 id="toc-hId--1074747546"><STRONG>8. </STRONG><STRONG>Executive Insight</STRONG></H3><P>Historically, utilities integrated applications.</P><P>The next generation utility will compete on the quality, trustworthiness, and usability of its data as much as the reliability of its physical infrastructure.</P><P>Organizations that establish trusted, governed, and reusable data products will be better positioned to deploy AI, automate operations, accelerate regulatory reporting, and improve customer experience. The competitive advantage will come not from possessing more data, but from creating a data architecture capable of turning data into operational intelligence.</P><H3 id="toc-hId--1271261051"><STRONG>9. </STRONG><STRONG>Final Thought</STRONG></H3><P>Utilities donāt need more data. They need <STRONG>better architecture</STRONG> for the data they already have.</P><P>A Utility Data Fabric on SAP BTP creates:</P><UL><LI>A unified data layer</LI><LI>A governed semantic foundation</LI><LI>A realātime operational backbone</LI><LI>A scalable platform for AI and analytics</LI></UL><P>In the next decade, utilities that master their data will lead. Those that donāt will struggle with reliability, cost, and customer expectations.</P><P><STRONG>Next in the Series ā ATUL Architecture #6</STRONG></P><P>We move into the intelligence layer:</P><UL><LI>AIādriven grid operations</LI><LI>Predictive models for BTP</LI><LI>Realātime decision engines</LI><LI>Digital twins</LI><LI>Autonomous workflows</LI></UL><P>This is where Data Fabric becomes the <STRONG>brain</STRONG> of the modern utility.</P><P> </P>2026-07-03T17:50:48.910000+02:00https://community.sap.com/t5/technology-blog-posts-by-sap/calling-abap-rfc-function-modules-from-sap-btp-kyma-with-sap-java-connector/ba-p/14437993Calling ABAP RFC Function Modules from SAP BTP Kyma with SAP Java Connector2026-07-10T16:11:31.874000+02:00Gunterhttps://community.sap.com/t5/user/viewprofilepage/user-id/727<H2 id="toc-hId-1819230782"><span class="lia-inline-image-display-wrapper lia-image-align-center" image-alt="Gemini_Generated_Image_w900egw900egw900.png" style="width: 999px;"><img src="https://community.sap.com/t5/image/serverpage/image-id/431706i882FAF5E4030B392/image-size/large?v=v2&px=999" role="button" title="Gemini_Generated_Image_w900egw900egw900.png" alt="Gemini_Generated_Image_w900egw900egw900.png" /></span></H2><H2 id="toc-hId-1622717277"> <SPAN>Why I Wrote This</SPAN></H2><P class="">I recently wanted to build a small demo application that proves a simple point:</P><BLOCKQUOTE dir="auto"><P class="">Can an application running on SAP BTP Kyma call an on-premise SAP system via RFC?</P></BLOCKQUOTE><P class="">At first glance, this sounds like something that should be straightforward. SAP Java Connector exists, SAP Cloud Connector exists, Kyma has Connectivity Proxy, and SAP BTP has Destination service. Surely we just package<SPAN> </SPAN><CODE>sapjco3.jar</CODE>, create a destination, and call<SPAN> </SPAN><CODE>STFC_CONNECTION</CODE>, right?</P><P class="">Well ā almost, but not quite.</P><P class="">While researching this, I found SAP Community questions such as<SPAN> </SPAN><A href="https://community.sap.com/t5/technology-q-a/how-can-i-connect-my-sap-btp-kyma-with-my-on-premise-system-ecc6-ehp7-via/qaq-p/12741996" target="_blank">āHow can I connect my SAP BTP Kyma with my On-Premise system (ECC6 EHP7) via RFCā</A>. The discussion captures the same uncertainty I ran into: Cloud Foundry examples exist, but Kyma is different because we build and run our own container images. There is no magic ābuilt JCoā unless we explicitly bring the right runtime pieces (I was happy to see my own github repo referenced in the question - another indication that there is nothing out there for RFC and Kyma).</P><P class="">This blog documents the working path I ended up with.</P><P class=""><STRONG><FONT color="#FF9900">Note: This is a proof of concept - SAP is not (yet) supporting this scenario. If that changes I will update the blog.</FONT></STRONG> </P><H2 id="the-goal" id="toc-hId-1426203772">The Goal</H2><P class="">The goal was intentionally small:</P><UL class=""><LI>Run a Java application in SAP BTP Kyma.</LI><LI>Resolve an RFC destination from SAP BTP Destination service.</LI><LI>Route RFC traffic through Kyma Connectivity Proxy.</LI><LI>Reach an on-premise SAP S/4HANA system via SAP Cloud Connector.</LI><LI>Execute<SPAN> </SPAN><CODE>STFC_CONNECTION</CODE>.</LI></UL><P class="">The successful response looked like this:</P><PRE><CODE><SPAN class="">{</SPAN>
<SPAN class="">"ok"</SPAN><SPAN class="">:</SPAN> <SPAN class=""><SPAN class="">true</SPAN></SPAN><SPAN class="">,</SPAN>
<SPAN class="">"destination"</SPAN><SPAN class="">:</SPAN> <SPAN class="">"InsightSuiteDestination"</SPAN><SPAN class="">,</SPAN>
<SPAN class="">"function"</SPAN><SPAN class="">:</SPAN> <SPAN class="">"STFC_CONNECTION"</SPAN><SPAN class="">,</SPAN>
<SPAN class="">"jcoVersion"</SPAN><SPAN class="">:</SPAN> <SPAN class="">"3.1.13.2 (2026-04-22) for SAP Business Technology Platform"</SPAN><SPAN class="">,</SPAN>
<SPAN class="">"exports"</SPAN><SPAN class="">:</SPAN> <SPAN class="">{</SPAN>
<SPAN class="">"ECHOTEXT"</SPAN><SPAN class="">:</SPAN> <SPAN class="">"Hello from Kyma"</SPAN><SPAN class="">,</SPAN>
<SPAN class="">"RESPTEXT"</SPAN><SPAN class="">:</SPAN> <SPAN class="">"SAP R/3 Rel. 816 Sysid: S4H ... Logon_Data: 201/RFC_TESTUSER/E"</SPAN>
<SPAN class="">}</SPAN>
<SPAN class="">}</SPAN>
</CODE></PRE><P class="">That was the happy end. The rest of this post is about how to get there.</P><H2 id="high-level-architecture" id="toc-hId-1229690267">High-Level Architecture</H2><P class="">The final request flow is:</P><PRE><CODE>HTTP client
-> Kyma Istio route
-> Java/Tomcat app in Kyma
-> SAP JCo cloud runtime
-> SAP BTP Destination service
-> Kyma Connectivity Proxy RFC port 20001
-> SAP Cloud Connector
-> on-premise SAP system RFC gateway
-> STFC_CONNECTION</CODE></PRE><P class="">For this demo, the relevant names were:</P><PRE><CODE>Kyma namespace: jco-rfc-test
RFC destination: InsightSuiteDestination
Cloud Connector ID: sap-insight-suite
Virtual RFC host: s4hana-2025japan
System number: 00
SAP client: 201
RFC function: STFC_CONNECTION</CODE></PRE><H2 id="key-lesson-1-plain-jco-is-not-enough" id="toc-hId-1033176762">Key Lesson 1: Plain JCo Is Not Enough</H2><P class="">My first attempt was the obvious one: package SAP JCo into a Tomcat image and run a servlet.</P><P class="">That was enough to start the app, but not enough to make the BTP/Kyma on-premise path work. The plain runtime did not know how to resolve BTP destinations and route RFC through the Kyma Connectivity Proxy with the required proxy authorization.</P><P class="">The working setup needed the SAP JCo cloud/connectivity runtime libraries that are normally provided by SAP Java Buildpack.</P><P class="">The important runtime ingredients were:</P><PRE><CODE>com.sap.conn.jco.cloud-3.1.13.2.jar
com.sap.conn.jco.cloud.rt.cloud-3.1.13.2.jar
com.sap.core.connectivity.jco-5.0.7.jar
com.sap.core.connectivity.jco.cf-5.0.7.jar
com.sap.core.connectivity.cloud.destinations.tunnel-0.9.267.jar</CODE></PRE><P class="">In my case these came from:</P><PRE><CODE>xs-java-buildpack-2.66.0-offline-cnb-tomcat.tgz</CODE></PRE><P class="">I used the buildpack archive as a runtime overlay and copied its Tomcat libraries into my image. If you have access to the official builder, using<SPAN> </SPAN><CODE>pack build</CODE><SPAN> </SPAN>is the cleaner path.</P><H2 id="the-minimal-java-code" id="toc-hId-836663257">The Minimal Java Code</H2><P class="">The Java code itself is pleasantly small. The servlet resolves a destination and calls a function module:</P><PRE><CODE><SPAN class="">JCoDestination</SPAN> <SPAN class="">destination</SPAN> <SPAN class="">=</SPAN> JCoDestinationManager.getDestination(<SPAN class="">"InsightSuiteDestination"</SPAN>);
<SPAN class="">JCoFunction</SPAN> <SPAN class="">function</SPAN> <SPAN class="">=</SPAN> destination.getRepository().getFunction(<SPAN class="">"STFC_CONNECTION"</SPAN>);
function.getImportParameterList().setValue(<SPAN class="">"REQUTEXT"</SPAN>, <SPAN class="">"Hello from Kyma"</SPAN>);
function.execute(destination);
<SPAN class="">String</SPAN> <SPAN class="">echoText</SPAN> <SPAN class="">=</SPAN> function.getExportParameterList().getString(<SPAN class="">"ECHOTEXT"</SPAN>);
<SPAN class="">String</SPAN> <SPAN class="">responseText</SPAN> <SPAN class="">=</SPAN> function.getExportParameterList().getString(<SPAN class="">"RESPTEXT"</SPAN>);</CODE></PRE><P class="">The demo app exposes this through an HTTP endpoint:</P><PRE><CODE>/rfc?destination=InsightSuiteDestination&text=Hello%20from%20Kyma</CODE></PRE><H2 id="build-setup" id="toc-hId-640149752">Build Setup</H2><P class="">The app is a WAR file running on Java 17 and Tomcat 10.1.</P><P class="">One important detail: SAP Java Buildpack 2.x uses the Jakarta servlet stack, so the application uses<SPAN> </SPAN><CODE>jakarta.servlet.*</CODE>, not<SPAN> </SPAN><CODE>javax.servlet.*</CODE>.</P><P class="">Relevant Maven setup:</P><PRE><CODE><SPAN class=""><<SPAN class="">packaging</SPAN>></SPAN>war<SPAN class=""></<SPAN class="">packaging</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">properties</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">maven.compiler.release</SPAN>></SPAN>17<SPAN class=""></<SPAN class="">maven.compiler.release</SPAN>></SPAN>
<SPAN class=""></<SPAN class="">properties</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">dependencies</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">dependency</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">groupId</SPAN>></SPAN>com.sap.conn.jco<SPAN class=""></<SPAN class="">groupId</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">artifactId</SPAN>></SPAN>sapjco3<SPAN class=""></<SPAN class="">artifactId</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">version</SPAN>></SPAN>3.1.13<SPAN class=""></<SPAN class="">version</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">scope</SPAN>></SPAN>system<SPAN class=""></<SPAN class="">scope</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">systemPath</SPAN>></SPAN>${project.basedir}/lib/sapjco3.jar<SPAN class=""></<SPAN class="">systemPath</SPAN>></SPAN>
<SPAN class=""></<SPAN class="">dependency</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">dependency</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">groupId</SPAN>></SPAN>jakarta.servlet<SPAN class=""></<SPAN class="">groupId</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">artifactId</SPAN>></SPAN>jakarta.servlet-api<SPAN class=""></<SPAN class="">artifactId</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">version</SPAN>></SPAN>6.1.0<SPAN class=""></<SPAN class="">version</SPAN>></SPAN>
<SPAN class=""><<SPAN class="">scope</SPAN>></SPAN>provided<SPAN class=""></<SPAN class="">scope</SPAN>></SPAN>
<SPAN class=""></<SPAN class="">dependency</SPAN>></SPAN>
<SPAN class=""></<SPAN class="">dependencies</SPAN>></SPAN>
</CODE></PRE><H2 id="container-image" id="toc-hId-443636247">Container Image</H2><P class="">The working image used Tomcat 10.1 and copied the SAP buildpack runtime overlay:</P><PRE><CODE><SPAN class="">FROM</SPAN> docker.io/library/maven:<SPAN class="">3.9</SPAN>-eclipse-temurin-<SPAN class="">17</SPAN> AS build
<SPAN class="">WORKDIR</SPAN><SPAN class="language-bash"> /workspace</SPAN>
<SPAN class="">COPY</SPAN><SPAN class="language-bash"> pom.xml ./</SPAN>
<SPAN class="">COPY</SPAN><SPAN class="language-bash"> lib ./lib</SPAN>
<SPAN class="">COPY</SPAN><SPAN class="language-bash"> src ./src</SPAN>
<SPAN class="">RUN</SPAN><SPAN class="language-bash"> mvn -q -DskipTests package</SPAN>
<SPAN class="">FROM</SPAN> docker.io/library/tomcat:<SPAN class="">10.1</SPAN>-jre17-temurin
<SPAN class="">ENV</SPAN> LD_LIBRARY_PATH=/usr/local/tomcat/impl/linuxx86_64:/usr/local/tomcat/lib \
JAVA_OPTS=<SPAN class="">"-Djava.library.path=/usr/local/tomcat/impl/linuxx86_64:/usr/local/tomcat/lib"</SPAN>
<SPAN class="">COPY</SPAN><SPAN class="language-bash"> .tmp-buildpack/tomcat/bin/ /usr/local/tomcat/bin/</SPAN>
<SPAN class="">COPY</SPAN><SPAN class="language-bash"> .tmp-buildpack/tomcat/conf/ /usr/local/tomcat/conf/</SPAN>
<SPAN class="">COPY</SPAN><SPAN class="language-bash"> .tmp-buildpack/tomcat/lib/ /usr/local/tomcat/lib/</SPAN>
<SPAN class="">COPY</SPAN><SPAN class="language-bash"> .tmp-buildpack/tomcat/impl/ /usr/local/tomcat/impl/</SPAN>
<SPAN class="">COPY</SPAN><SPAN class="language-bash"> --from=build /workspace/target/jco-on-kyma-demo.war /usr/local/tomcat/webapps/ROOT.war</SPAN>
<SPAN class="">EXPOSE</SPAN> <SPAN class="">8080</SPAN>
</CODE></PRE><P class="">Before building, extract the buildpack archive:</P><PRE><CODE><SPAN class="">mkdir</SPAN> -p .tmp-buildpack/tomcat
tar -xzf java-buildpack/xs-java-buildpack-2.66.0-offline-cnb-tomcat.tgz \
-C .tmp-buildpack/tomcat</CODE></PRE><P class="">Then build and push the image to a registry Kyma can pull from.</P><H2 id="kyma-service-bindings" id="toc-hId-247122742">Kyma Service Bindings</H2><P class="">The application needs at least:</P><UL class=""><LI>Destination service instance and binding</LI><LI>XSUAA service instance and binding</LI><LI>Connectivity Proxy config and secret available in the application namespace</LI></UL><P class="">Example BTP service resources:</P><PRE><CODE><SPAN class="">apiVersion:</SPAN> <SPAN class="">services.cloud.sap.com/v1</SPAN>
<SPAN class="">kind:</SPAN> <SPAN class="">ServiceInstance</SPAN>
<SPAN class="">metadata:</SPAN>
<SPAN class="">name:</SPAN> <SPAN class="">destination-jco</SPAN>
<SPAN class="">namespace:</SPAN> <SPAN class="">jco-rfc-test</SPAN>
<SPAN class="">spec:</SPAN>
<SPAN class="">serviceOfferingName:</SPAN> <SPAN class="">destination</SPAN>
<SPAN class="">servicePlanName:</SPAN> <SPAN class="">lite</SPAN>
<SPAN class="">---</SPAN>
<SPAN class="">apiVersion:</SPAN> <SPAN class="">services.cloud.sap.com/v1</SPAN>
<SPAN class="">kind:</SPAN> <SPAN class="">ServiceBinding</SPAN>
<SPAN class="">metadata:</SPAN>
<SPAN class="">name:</SPAN> <SPAN class="">destination-jco</SPAN>
<SPAN class="">namespace:</SPAN> <SPAN class="">jco-rfc-test</SPAN>
<SPAN class="">spec:</SPAN>
<SPAN class="">serviceInstanceName:</SPAN> <SPAN class="">destination-jco</SPAN>
<SPAN class="">---</SPAN>
<SPAN class="">apiVersion:</SPAN> <SPAN class="">services.cloud.sap.com/v1</SPAN>
<SPAN class="">kind:</SPAN> <SPAN class="">ServiceInstance</SPAN>
<SPAN class="">metadata:</SPAN>
<SPAN class="">name:</SPAN> <SPAN class="">xsuaa-jco</SPAN>
<SPAN class="">namespace:</SPAN> <SPAN class="">jco-rfc-test</SPAN>
<SPAN class="">spec:</SPAN>
<SPAN class="">serviceOfferingName:</SPAN> <SPAN class="">xsuaa</SPAN>
<SPAN class="">servicePlanName:</SPAN> <SPAN class="">application</SPAN>
<SPAN class="">---</SPAN>
<SPAN class="">apiVersion:</SPAN> <SPAN class="">services.cloud.sap.com/v1</SPAN>
<SPAN class="">kind:</SPAN> <SPAN class="">ServiceBinding</SPAN>
<SPAN class="">metadata:</SPAN>
<SPAN class="">name:</SPAN> <SPAN class="">xsuaa-jco</SPAN>
<SPAN class="">namespace:</SPAN> <SPAN class="">jco-rfc-test</SPAN>
<SPAN class="">spec:</SPAN>
<SPAN class="">serviceInstanceName:</SPAN> <SPAN class="">xsuaa-jco</SPAN>
</CODE></PRE><P class="">The SAP runtime expected the bindings as files. Injecting them only as environment variables was not sufficient in my setup.</P><PRE><CODE><SPAN class="">-</SPAN> <SPAN class="">name:</SPAN> <SPAN class="">SERVICE_BINDING_ROOT</SPAN>
<SPAN class="">value:</SPAN> <SPAN class="">"/etc/secrets/sapbtp"</SPAN>
</CODE></PRE><P class="">And mount the binding secrets:</P><PRE><CODE><SPAN class="">volumeMounts:</SPAN>
<SPAN class="">-</SPAN> <SPAN class="">name:</SPAN> <SPAN class="">destination-binding</SPAN>
<SPAN class="">mountPath:</SPAN> <SPAN class="">/etc/secrets/sapbtp/destination-jco</SPAN>
<SPAN class="">readOnly:</SPAN> <SPAN class="">true</SPAN>
<SPAN class="">-</SPAN> <SPAN class="">name:</SPAN> <SPAN class="">xsuaa-binding</SPAN>
<SPAN class="">mountPath:</SPAN> <SPAN class="">/etc/secrets/sapbtp/xsuaa-jco</SPAN>
<SPAN class="">readOnly:</SPAN> <SPAN class="">true</SPAN>
<SPAN class="">volumes:</SPAN>
<SPAN class="">-</SPAN> <SPAN class="">name:</SPAN> <SPAN class="">destination-binding</SPAN>
<SPAN class="">secret:</SPAN>
<SPAN class="">secretName:</SPAN> <SPAN class="">destination-jco</SPAN>
<SPAN class="">-</SPAN> <SPAN class="">name:</SPAN> <SPAN class="">xsuaa-binding</SPAN>
<SPAN class="">secret:</SPAN>
<SPAN class="">secretName:</SPAN> <SPAN class="">xsuaa-jco</SPAN>
</CODE></PRE><H2 id="connectivity-proxy-environment" id="toc-hId-50609237">Connectivity Proxy Environment</H2><P class="">For RFC, the app uses Connectivity Proxy port<SPAN> </SPAN><CODE>20001</CODE>:</P><PRE><CODE><SPAN class="">-</SPAN> <SPAN class="">name:</SPAN> <SPAN class="">ONPREMISE_PROXY_HOST</SPAN>
<SPAN class="">valueFrom:</SPAN>
<SPAN class="">configMapKeyRef:</SPAN>
<SPAN class="">name:</SPAN> <SPAN class="">connectivity-proxy-info</SPAN>
<SPAN class="">key:</SPAN> <SPAN class="">onpremise_proxy_host</SPAN>
<SPAN class="">-</SPAN> <SPAN class="">name:</SPAN> <SPAN class="">ONPREMISE_PROXY_RFC_PORT</SPAN>
<SPAN class="">valueFrom:</SPAN>
<SPAN class="">configMapKeyRef:</SPAN>
<SPAN class="">name:</SPAN> <SPAN class="">connectivity-proxy-info</SPAN>
<SPAN class="">key:</SPAN> <SPAN class="">onpremise_proxy_rfc_port</SPAN>
<SPAN class="">-</SPAN> <SPAN class="">name:</SPAN> <SPAN class="">ONPREMISE_PROXY_TENANT_MODE</SPAN>
<SPAN class="">valueFrom:</SPAN>
<SPAN class="">configMapKeyRef:</SPAN>
<SPAN class="">name:</SPAN> <SPAN class="">connectivity-proxy-info</SPAN>
<SPAN class="">key:</SPAN> <SPAN class="">onpremise_proxy_tenant_mode</SPAN>
<SPAN class="">-</SPAN> <SPAN class="">name:</SPAN> <SPAN class="">CP_SECRET_CONNECTIVITY_PROXY_KYMA_SYSTEM</SPAN>
<SPAN class="">valueFrom:</SPAN>
<SPAN class="">secretKeyRef:</SPAN>
<SPAN class="">name:</SPAN> <SPAN class="">cp-secret-connectivity-proxy-kyma-system</SPAN>
<SPAN class="">key:</SPAN> <SPAN class="">service_key</SPAN>
</CODE></PRE><P class="">In this demo the proxy endpoint was:</P><PRE><CODE>connectivity-proxy.kyma-system.svc.cluster.local:20001</CODE></PRE><H2 id="creating-the-rfc-destination" id="toc-hId-201350089">Creating the RFC Destination</H2><P class="">This part is easy to miss.</P><P class="">The RFC destination is not a Kubernetes<SPAN> </SPAN><CODE>Destination</CODE><SPAN> </SPAN>custom resource. For this JCo flow, the destination must exist in SAP BTP Destination service, and JCo resolves it from there.</P><P class="">The working destination properties were:</P><PRE><CODE><SPAN class="">Name</SPAN>=<SPAN class="">InsightSuiteDestination</SPAN>
<SPAN class="">Type</SPAN>=<SPAN class="">RFC</SPAN>
<SPAN class="">jco.client.ashost</SPAN>=<SPAN class="">s4hana-2025japan</SPAN>
<SPAN class="">jco.client.sysnr</SPAN>=<SPAN class="">00</SPAN>
<SPAN class="">jco.client.client</SPAN>=<SPAN class="">201</SPAN>
<SPAN class="">jco.client.user</SPAN>=<SPAN class="">RFC_TESTUSER</SPAN>
<SPAN class="">jco.client.passwd</SPAN>=<SPAN class=""><password></SPAN>
<SPAN class="">jco.client.lang</SPAN>=<SPAN class="">EN</SPAN>
<SPAN class="">jco.destination.proxy_type</SPAN>=<SPAN class="">OnPremise</SPAN>
<SPAN class="">jco.client.cloud_connector_location_id</SPAN>=<SPAN class="">sap-insight-suite</SPAN>
</CODE></PRE><P class="">The most important line for the Location ID was:</P><PRE><CODE><SPAN class="">jco.client.cloud_connector_location_id</SPAN>=<SPAN class="">sap-insight-suite</SPAN>
</CODE></PRE><P class="">Initially, I used only:</P><PRE><CODE><SPAN class="">CloudConnectorLocationId</SPAN>=<SPAN class="">sap-insight-suite</SPAN>
</CODE></PRE><P class="">That appeared as a raw/custom property but did not populate the RFC destination's Location ID field in SAP BTP Cockpit. The tunnel was opened as if the location ID was empty.</P><P class="">After adding<SPAN> </SPAN><CODE>jco.client.cloud_connector_location_id</CODE>, Cockpit showed the Location ID correctly, and Connectivity Proxy opened the expected tunnel:</P><PRE><CODE>account:///.../sap-insight-suite</CODE></PRE><P class="">In the repository I created a small helper script to create the destination through the Destination service API:</P><PRE><CODE><SPAN class="">export</SPAN> RFC_PASSWORD=<SPAN class="">'<password>'</SPAN>
./scripts/upsert-rfc-destination.py \
--namespace jco-rfc-test \
--binding-secret destination-jco \
--template destinations/InsightSuiteDestination.template.json \
--delete-first
<SPAN class="">unset</SPAN> RFC_PASSWORD</CODE></PRE><H2 id="connectivity-proxy-authorization" id="toc-hId-4836584">Connectivity Proxy Authorization</H2><P class="">Another issue I hit was proxy authorization. The Connectivity Proxy log showed:</P><PRE><CODE>Validation of token failed due to: Allowed client ids not set for region configuration id default</CODE></PRE><P class="">The fix was to configure the allowed client ID in the<SPAN> </SPAN><CODE>ConnectivityProxy</CODE><SPAN> </SPAN>custom resource:</P><PRE><CODE>CLIENTID=$(kubectl -n kyma-system get secret cp-secret-connectivity-proxy-kyma-system \
-o jsonpath=<SPAN class="">'{.data.service_key}'</SPAN> \
| <SPAN class="">base64</SPAN> -d \
| python3 -c <SPAN class="">'import json,sys; print(json.load(sys.stdin)["clientid"])'</SPAN>)
python3 - <<<SPAN class="">PY > /tmp/cp-patch.json
import json
clientid = "$CLIENTID"
print(json.dumps({
"spec": {
"config": {
"servers": {
"proxy": {
"authorization": {
"oauth": {
"allowedClientId": clientid
}
}
}
}
}
}
}))
PY</SPAN>
kubectl -n kyma-system patch connectivityproxy connectivity-proxy \
--<SPAN class="">type</SPAN> merge \
--patch-file /tmp/cp-patch.json</CODE></PRE><P class="">After this, the proxy accepted the token and opened the RFC tunnel.</P><H2 id="how-to-recognize-success" id="toc-hId--191676921">How to Recognize Success</H2><P class="">Application logs showed:</P><PRE><CODE>Connected to socket with {sap-insight-suite-RFC.connectivity,2}
RfcOpen(... SCCLOCATIONID=sap-insight-suite)=1
Executing function STFC_CONNECTION</CODE></PRE><P class="">Connectivity Proxy logs showed:</P><PRE><CODE>Will use tunnelId: account:///.../sap-insight-suite
Handshake with tunnel client completed successfully</CODE></PRE><P class="">And the endpoint returned:</P><PRE><CODE><SPAN class="">{</SPAN>
<SPAN class="">"ok"</SPAN><SPAN class="">:</SPAN> <SPAN class=""><SPAN class="">true</SPAN></SPAN><SPAN class="">,</SPAN>
<SPAN class="">"exports"</SPAN><SPAN class="">:</SPAN> <SPAN class="">{</SPAN>
<SPAN class="">"ECHOTEXT"</SPAN><SPAN class="">:</SPAN> <SPAN class="">"Hello from Kyma"</SPAN>
<SPAN class="">}</SPAN>
<SPAN class="">}</SPAN>
</CODE></PRE><H2 id="things-i-would-watch-out-for" id="toc-hId--388190426">Things I Would Watch Out For</H2><P class="">Here are the stumbling blocks that cost the most time:</P><OL class=""><LI><P class=""><STRONG>Plain JCo is not enough</STRONG><BR />You need the SAP JCo cloud/connectivity runtime, not only<SPAN> </SPAN><CODE>sapjco3.jar</CODE><SPAN> </SPAN>and<SPAN> </SPAN><CODE>libsapjco3.so</CODE>.</P></LI><LI><P class=""><STRONG>SAP Java Buildpack 2.x means Jakarta</STRONG><BR />If you use the 2.x runtime libraries, use Java 17 and<SPAN> </SPAN><CODE>jakarta.servlet.*</CODE>.</P></LI><LI><P class=""><STRONG>Mount service bindings as files</STRONG><BR />The runtime looked for service bindings under<SPAN> </SPAN><CODE>/etc/secrets/sapbtp</CODE>.</P></LI><LI><P class=""><STRONG>Use the RFC-specific Location ID property</STRONG><BR />For RFC destinations, use<SPAN> </SPAN><CODE>jco.client.cloud_connector_location_id</CODE>.</P></LI><LI><P class=""><STRONG>Connectivity Proxy may need allowed client ID configuration</STRONG><BR />If you see<SPAN> </SPAN><CODE>Allowed client ids not set</CODE>, patch the<SPAN> </SPAN><CODE>ConnectivityProxy</CODE><SPAN> </SPAN>configuration.</P></LI><LI><P class=""><STRONG>Reduce debug logging after troubleshooting</STRONG><BR />JCo/connectivity debug logs can expose destination details and token payloads.</P></LI></OL><H2 id="final-thoughts" id="toc-hId--584703931">Final Thoughts</H2><P class="">Once all pieces were in place, the actual RFC call was simple. The hard part was not the Java code; it was assembling the correct runtime and configuration around it.</P><P class="">The most important takeaway for me was this:</P><BLOCKQUOTE dir="auto"><P class="">In Kyma, RFC via JCo is possible, but you need the SAP cloud JCo/connectivity runtime and a correctly maintained BTP RFC destination.</P></BLOCKQUOTE><P class="">Hopefully this saves someone else a few debugging loops ā especially around the Location ID and Connectivity Proxy authorization details.</P><H2 id="appendix-minimal-checklist" id="toc-hId--781217436">Appendix: Minimal Checklist</H2><UL class=""><LI>[ ] Connectivity Proxy module is installed and RFC port<SPAN> </SPAN><CODE>20001</CODE><SPAN> </SPAN>is enabled.</LI><LI>[ ] SAP Cloud Connector is connected to the correct subaccount.</LI><LI>[ ] Cloud Connector exposes the virtual RFC host/system.</LI><LI>[ ] Destination service instance and binding exist in the app namespace.</LI><LI>[ ] XSUAA service instance and binding exist in the app namespace.</LI><LI>[ ] Service bindings are mounted under<SPAN> </SPAN><CODE>/etc/secrets/sapbtp</CODE>.</LI><LI>[ ] App image includes SAP JCo cloud/connectivity runtime libraries.</LI><LI>[ ] RFC destination exists in BTP Destination service.</LI><LI>[ ] RFC destination has<SPAN> </SPAN><CODE>jco.client.cloud_connector_location_id</CODE><SPAN> </SPAN>if a Location ID is used.</LI><LI>[ ] Connectivity Proxy authorization allows the connectivity client ID.</LI><LI>[ ]<SPAN> </SPAN><CODE>STFC_CONNECTION</CODE><SPAN> </SPAN>works with the configured SAP user.</LI></UL>2026-07-10T16:11:31.874000+02:00