SAP S/4HANA Cloud OData connector — read-only agent access to FI/CO/MM/SD entities for financial and operational analytics
Project description
piilot-pack-sap
SAP S/4HANA Cloud OData connector for Piilot — read-only agent access to FI / CO / MM / SD entities for financial and operational analytics.
What it does
Connect your Piilot agents to a SAP S/4HANA Cloud instance via the
standard OData v4 API. Read-only access to financial entities
(A_JournalEntry, A_GLAccountLineItem, A_BusinessPartner, …),
controlling entities (A_CostCenter, A_ProfitCenter, …) and
operational entities across MM / SD modules.
The plugin contributes :
- 1 module Piilot
sap.connector— connection config (auth + endpoint), status panel, OData entity browser, audit log. - 1 connector
sap.s4hana_cloud— Basic auth or OAuth 2.0client_credentialsagainst your SAP S/4HANA Cloud instance. Credentials encrypted at the DB boundary by the Piilot core (Fernet). - 9 agent tools (Phase 2 deliverable) —
sap_search_entity,sap_describe_entity,sap_select,sap_count,sap_aggregate,sap_top_n,sap_navigate,sap_lookup,sap_invoke_function. All read-only. Strict whitelist OData validator (no$expand, no$batch, no function imports en écriture, no mutations). - 1 KB plugin-owned "SAP metadata" (Phase 1 deliverable) — embedded
introspection of your SAP
$metadatafor RAG-driven entity discovery.
Pattern : 1 plugin, 1 module Piilot. Aligned with
piilot-pack-pennylane (treasury dashboard) and piilot-pack-supabase
(connector + tools). See AICockpit/docs/sdk/PLUGIN_DEVELOPMENT.md
§20 for the SDK frontend contract.
Scope v1
| Variant | OData v4 | v1 support |
|---|---|---|
| SAP S/4HANA Cloud | ✅ Full standard | ✅ Cible v1 |
| SAP S/4HANA on-premise | ✅ via Cloud Connector / API Mgmt | 🟡 v2+ |
| SAP ECC (R/3 legacy) | 🟡 OData v2 partiel + BAPIs RFC | ❌ v2+ (probably another plugin) |
Auth modes shipped in v1 :
basic— username + password, sandbox-friendly + smaller installs.oauth_client_credentials— standard SAP S/4HANA Cloud productive Communication User. Token URL + Client ID/Secret + scope.
X.509 cert auth is deferred to v1.1.
Status
🟢 v0.1.0 — Phases 0 + 1 + 2 + 3 + 4 ready (pending tag)
What ships:
- 3 tables plugin-owned schema
integrations_sap:connections,schema_snapshot,audit_log. RLS enforced. - Live OData v2 / v4 client with auto-version detection,
retries on 429+5xx (
Retry-After-aware), three auth strategies (ApiKey,Basic,OAuth client_credentials). - Strict whitelist validator — fails closed on
$expand,$batch, function calls, lambda operators, navigation paths, mutations. Fuzzed against 14 attack payloads. - 11 HTTP endpoints under
/plugins/sap/*(CRUD + test + sync + entities + audit), gated byrequire_user/require_builder/require_admin, rate-limited per company. - 9 agent tools (cf. table below) wrapped with
bind_session, audited on every call, capped by a per-session cost guard (default 30 calls). - Frontend
SAPConnectorViewwith 4 shadcnTabs: Connection / Status / Browser / Audit. URL-driven sub-routing. - Plugin-owned KB "SAP Metadata — <connection>" auto-seeded on first sync, refreshed idempotently on every re-sync.
Test coverage: ~400 unit tests (Python + TypeScript) + 5 live sandbox tests at 95% Python coverage.
See AICockpit/docs/docs_dev/suivi.md (internal) for the phase-by-phase
journal.
Install
Self-hosted Piilot
pip install piilot-pack-sap
docker compose restart backend
Piilot Cloud (SaaS)
Pinned in core's backend/api/requirements.txt:
piilot-pack-sap==0.1.0
Frontend pinned in core's frontend/package.json:
"piilot-pack-sap-ui": "^0.1.0"
Then the core's vite.config.ts resolves @plugin/sap via its 3-tier
alias (dev bind-mount → npm tier → noop shim). See
AICockpit/docs/sdk/PLUGIN_DEV_WORKFLOW.md §5.
Enable for a given company via the activation API :
curl -X PUT \
-H "Authorization: Bearer $ADMIN_TOKEN" \
https://api.piilot.ai/admin/plugins/sap/activations/$COMPANY_ID
Configuration
A Piilot admin connects an SAP S/4HANA Cloud instance from the
plugin's module view (/modules/<sap-connector-uuid> once the plugin
is activated).
Required fields :
| Field | Description |
|---|---|
| Auth mode | basic or oauth_client_credentials |
| Base URL | Your S/4HANA Cloud tenant URL (e.g. https://my123456.s4hana.cloud.sap) |
| Basic — username / password | If auth_mode = basic. Communication User created in SAP. |
| OAuth — token URL / client ID / client secret / scope | If auth_mode = oauth_client_credentials. Configure a productive Communication Arrangement in SAP. |
Credentials are encrypted at rest (Fernet) by the core's
plugin_connections table. The plugin never stores raw secrets in
its own integrations_sap.connections row.
Required SAP role : the Communication User (Basic) or the Communication Arrangement (OAuth) MUST have a PFCG role restricted to read-only access on the EntitySets you want Piilot to consume. See the SAP-side setup section below.
SAP-side setup
Option A — Basic auth Communication User
Quick way to onboard, suitable for trial instances or sandboxed environments. NOT recommended for productive S/4HANA Cloud tenants (use OAuth client_credentials instead).
- SAP S/4HANA Cloud — log in with an admin user.
- Open the Maintain Communication Users app
(transaction code
SU05on-premise; Communication Management tile on Cloud). - Create a technical user named e.g.
PIILOT_AGENT. Generate a strong password (≥ 24 chars). - Go to Communication Systems and create a system pointing at Piilot's outbound IP range (your tenant's egress).
- Go to Communication Arrangements and assign every OData service
you want to expose (e.g.
SAP_COM_0507for Business Partner,SAP_COM_0019for General Ledger). - Pick the technical user
PIILOT_AGENTin the Arrangement's Inbound Communication section. - Note the service URL (base URL) printed at the bottom of each
Arrangement page. Format:
https://<tenant>-api.s4hana.cloud.sap/sap/opu/odata/sap/<SERVICE>. - In the Piilot UI, Connection tab → + New connection →
choose
auth_mode = basic, paste the URL + username + password.
Option B — OAuth 2.0 client_credentials (recommended)
Standard for productive S/4HANA Cloud. Token rotation handled by SAP BTP; no shared passwords on the wire.
- SAP BTP Cockpit → open the XSUAA service for the subaccount that hosts the S/4HANA Cloud subscription.
- Create a new service instance with plan
application. Thexs-security.jsonshould expose areadscope per OData service. - Create a service key on the instance. The key contains :
clientid→ Piilot's "Client ID"clientsecret→ Piilot's "Client Secret"url→ token endpoint; append/oauth/token→ Piilot's "Token URL"
- Bind the BTP scope to a Communication Arrangement in S/4HANA Cloud (same arrangement as Option A, but check "OAuth 2.0 Client Credentials Grant" instead of "Basic").
- In the Piilot UI, Connection tab → + New connection →
choose
auth_mode = oauth_client_credentials, paste the four fields (token_url,client_id,client_secret, optionalscope).
Verify connectivity
After saving the connection, open the Status tab and click
Test connection. The route fetches $metadata once and returns
either ok + entity_set_count, or a structured error. If it works,
click Re-sync $metadata to populate the local snapshot cache and
seed the plugin-owned KB.
Agent tools — the 9 OData verbs
Every tool is exposed to Piilot agents as sap_<name> (LangChain
StructuredTool). session_id is injected by the runtime
(bind_session) so the LLM never picks the connection.
| Tool | Purpose | Sample agent prompt |
|---|---|---|
sap_search_entity |
substring search the cached EntitySet catalogue | "Trouve l'entité SAP qui contient les factures clients." |
sap_describe_entity |
return cached $metadata for one EntitySet |
"Quelles colonnes a A_BusinessPartner ?" |
sap_select |
filtered + projected GET | "Liste les 10 premiers BP créés en 2026." |
sap_count |
row count | "Combien de BP catégorie '2' ?" |
sap_top_n |
wrapper $top + $orderby | "Top 5 commandes par montant décroissant." |
sap_aggregate |
$apply=aggregate(...) |
"Somme des montants par mois." |
sap_navigate |
follow a Navigation Property | "Adresses du BP '11'." |
sap_lookup |
admin only: single record by primary key including technical fields | "Fetch the raw A_GLAccount row '400000'." |
sap_invoke_function |
admin only: invoke a read-only OData function import | "Run ComputeBalance(CompanyCode='1000', Year=2026)." |
All tools return a structured dict the LLM can parse directly:
{
"status": "ok",
"data": { ... },
"connection_label": "Sandbox",
"audit_id": "uuid"
}
Possible status values: ok / validator_rejected /
auth_error / http_error / rate_limited / timeout /
parse_error / resolution_error / session_unknown /
forbidden / cost_limit_exceeded / not_found.
Cost guard
Every call increments a per-session counter. The default cap is
30 tool calls per session (configurable via the
SAP_TOOL_BUDGET_PER_SESSION env var). Beyond the cap, the tool
returns status="cost_limit_exceeded" without hitting SAP. Sessions
are short-lived (≤30 min by host policy), so the counter resets
naturally.
Rate limit
HTTP routes enforce per-company sliding-window limits:
- GET endpoints — 60/min
- POST / PATCH / DELETE — 10/min
- POST
/testand/sync— 5/min
429 responses carry a Retry-After header.
Troubleshooting
HTTP 403 — UCON blocked
SAP gateway's UCON filter rejected the request. Common causes:
- The Communication Arrangement does not expose the EntitySet you hit (check the service whitelist on the Arrangement screen).
- You're hitting an OData v4 endpoint on a tenant that only exposes v2 catalog entries.
- The technical user doesn't have the right PFCG role.
Fix: re-open the Communication Arrangement, add the service or the role, save, then click Test connection again.
HTTP 401 — Unauthorized
- Basic : the password rotated. Update via PATCH
/plugins/sap/connections/{id}withcredentials.basic_password. - OAuth : the BTP service key was deleted or rotated. Re-create it on BTP, then PATCH the four oauth fields.
HTTP 406 — Not Acceptable on $metadata
You're not hitting this — the plugin sends Accept: application/xml
on $metadata automatically. If you ever see it through a custom
client, switch the Accept header.
parse_error after Test connection
The $metadata response wasn't valid XML. Either the endpoint
returned an HTML error page (login redirect, WAF block) or the
service is misconfigured. Check the underlying response with curl:
curl -u user:pass -v https://<tenant>/sap/opu/odata/sap/API_BP/\$metadata
validator_rejected on every agent call
The agent is calling sap_select with a $filter that uses a
function call (e.g. contains(Name, 'Foo')). The v1 plugin refuses
function calls on purpose — guide the agent towards equality /
range comparisons (Name eq 'Foo', CreationDate ge datetime'...').
cost_limit_exceeded on every call after a while
The session counter is exhausted. Either the agent is in a loop
(check the audit log for the same query repeated), or your budget
is too low. Bump SAP_TOOL_BUDGET_PER_SESSION to e.g. 100 if your
workflow legitimately needs many calls.
403 X-Company-Id on routes
The Piilot host's plugin_gate middleware refused the request.
Check that:
- The Authorization header is valid.
- The
X-Company-Idheader matches a company the user belongs to. - The plugin is activated for that company
(
companies_plugins.enabled = trueforprovider='sap.s4hana_cloud').
Development
Local setup
# Clone next to the Piilot core
cd /opt/factory/projects/AICockpit/workspaces/<workspace>/plugins-dev
git clone https://github.com/Kinetics-Consulting-V2/piilot-pack-sap.git
cd piilot-pack-sap
# Install in editable mode with dev extras
pip install -e .[dev]
# Frontend (peer deps only, no install needed for source-only npm)
cd frontend && npm install --no-package-lock # for vitest local
Bind-mount the plugin into the core's compose.dev.yml (gitignored
by AICockpit) :
services:
backend:
volumes:
- ../plugins-dev/piilot-pack-sap:/app/plugins/piilot-pack-sap
Restart the backend and watch for :
[plugins] Loaded: sap v0.1.0 (handlers=1, tools=0, modules=1, connectors=1)
Run the tests
# Backend
pytest
# Frontend
cd frontend && npm test
Versioning
This plugin follows Semantic Versioning. The
sdk_compat range in pyproject.toml pins the Piilot SDK versions
we build against. v0.1.0 pins piilot-sdk>=0.7.0,<1.0.0. Watch the
core's SDK changelog for breaking changes.
Dual distribution
- Backend —
piilot-pack-sapon PyPI, taggedv<version>. - Frontend —
piilot-pack-sap-uion npm, taggedui-v<version>.
Both ship from this single repo; the two release workflows publish independently via OIDC Trusted Publisher (PyPI) and Classic Automation token (npm).
License
Apache-2.0. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file piilot_pack_sap-0.1.2.tar.gz.
File metadata
- Download URL: piilot_pack_sap-0.1.2.tar.gz
- Upload date:
- Size: 100.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a8cd9e6114c5a3e30f8f37b4c548a90f86ff49f2f7e34c1198a10fdfe17ec9fe
|
|
| MD5 |
9e4619e9d5a60f421e9768e91c306ffb
|
|
| BLAKE2b-256 |
1a6be017d52e5c5bb6cf0dcbabca1c999cc49c664605898366fa085237d056a8
|
Provenance
The following attestation bundles were made for piilot_pack_sap-0.1.2.tar.gz:
Publisher:
release.yml on Kinetics-Consulting-V2/piilot-pack-sap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
piilot_pack_sap-0.1.2.tar.gz -
Subject digest:
a8cd9e6114c5a3e30f8f37b4c548a90f86ff49f2f7e34c1198a10fdfe17ec9fe - Sigstore transparency entry: 1573476858
- Sigstore integration time:
-
Permalink:
Kinetics-Consulting-V2/piilot-pack-sap@e009b15c43d12488f449829e3a3dcd47575d927b -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/Kinetics-Consulting-V2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e009b15c43d12488f449829e3a3dcd47575d927b -
Trigger Event:
push
-
Statement type:
File details
Details for the file piilot_pack_sap-0.1.2-py3-none-any.whl.
File metadata
- Download URL: piilot_pack_sap-0.1.2-py3-none-any.whl
- Upload date:
- Size: 72.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
40cac8a39a0b930cad6598ba0bbd679c0969ced46e9ae32c6c95618cfa10ab51
|
|
| MD5 |
50d4082eca6c5a6c16d29be1ccf49982
|
|
| BLAKE2b-256 |
5974dfeff68bf382b73ac1c18b244007e533eb44e1aa66b3e78e67f3c2f4e690
|
Provenance
The following attestation bundles were made for piilot_pack_sap-0.1.2-py3-none-any.whl:
Publisher:
release.yml on Kinetics-Consulting-V2/piilot-pack-sap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
piilot_pack_sap-0.1.2-py3-none-any.whl -
Subject digest:
40cac8a39a0b930cad6598ba0bbd679c0969ced46e9ae32c6c95618cfa10ab51 - Sigstore transparency entry: 1573476904
- Sigstore integration time:
-
Permalink:
Kinetics-Consulting-V2/piilot-pack-sap@e009b15c43d12488f449829e3a3dcd47575d927b -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/Kinetics-Consulting-V2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e009b15c43d12488f449829e3a3dcd47575d927b -
Trigger Event:
push
-
Statement type: