Skip to main content

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.

PyPI npm License: Apache-2.0


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.0 client_credentials against 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 $metadata for 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 by require_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 SAPConnectorView with 4 shadcn Tabs: 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).

  1. SAP S/4HANA Cloud — log in with an admin user.
  2. Open the Maintain Communication Users app (transaction code SU05 on-premise; Communication Management tile on Cloud).
  3. Create a technical user named e.g. PIILOT_AGENT. Generate a strong password (≥ 24 chars).
  4. Go to Communication Systems and create a system pointing at Piilot's outbound IP range (your tenant's egress).
  5. Go to Communication Arrangements and assign every OData service you want to expose (e.g. SAP_COM_0507 for Business Partner, SAP_COM_0019 for General Ledger).
  6. Pick the technical user PIILOT_AGENT in the Arrangement's Inbound Communication section.
  7. 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>.
  8. 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.

  1. SAP BTP Cockpit → open the XSUAA service for the subaccount that hosts the S/4HANA Cloud subscription.
  2. Create a new service instance with plan application. The xs-security.json should expose a read scope per OData service.
  3. 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"
  4. 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").
  5. In the Piilot UI, Connection tab → + New connection → choose auth_mode = oauth_client_credentials, paste the four fields (token_url, client_id, client_secret, optional scope).

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 /test and /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} with credentials.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-Id header matches a company the user belongs to.
  • The plugin is activated for that company (companies_plugins.enabled = true for provider='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

  • Backendpiilot-pack-sap on PyPI, tagged v<version>.
  • Frontendpiilot-pack-sap-ui on npm, tagged ui-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.

Release files for piilot-pack-sap 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for piilot-pack-sap 0.1.2
File Size Uploaded
piilot_pack_sap-0.1.2.tar.gz 100.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for piilot-pack-sap 0.1.2
File Interpreter ABI Platform
piilot_pack_sap-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 172.5 kB

Release files / piilot_pack_sap-0.1.2.tar.gz

Download URL piilot_pack_sap-0.1.2.tar.gz
Size 100.1 kB
Tags Source
SHA-256 checksum
How to use checksums
a8cd9e6114c5a3e30f8f37b4c548a90f86ff49f2f7e34c1198a10fdfe17ec9fe
BLAKE2b-256 checksum
How to use checksums
1a6be017d52e5c5bb6cf0dcbabca1c999cc49c664605898366fa085237d056a8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 19, 2026.

Transparency log

Release files / piilot_pack_sap-0.1.2-py3-none-any.whl

Download URL piilot_pack_sap-0.1.2-py3-none-any.whl
Size 72.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
40cac8a39a0b930cad6598ba0bbd679c0969ced46e9ae32c6c95618cfa10ab51
BLAKE2b-256 checksum
How to use checksums
5974dfeff68bf382b73ac1c18b244007e533eb44e1aa66b3e78e67f3c2f4e690
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 19, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page