Skip to main content
Wildo.ai Coming soon

Systems of record

Read SAP S/4HANA data in your application

Connect the SAP S/4HANA system a company already runs, so an application can show its records live or keep its own copy up to date. Reading is supported; writing back into SAP is not.

Partially available today — the limit is described on this page.

SAP business partners are copied hourly into a project as locked records, with separate team notes kept in the application.

Read SAP S/4HANA data in your application

A company running SAP S/4HANA can build internal tools on the records SAP already holds. An application declares the SAP system once, then each resource that needs SAP data either reads it live or keeps a copy.

Read in place: a list or record screen asks SAP at the moment it opens, so what people see is what SAP holds now. Copy kept current: a scheduled pipeline copies selected fields into the application’s own store, where they can sit beside fields only the application owns, such as an internal note or a status.

SAP publishes many of its most used services in the older OData version 2 and newer ones in version 4. Both are read. Version 2 values arrive as text, so Wildo reads each property’s type from the service’s own description and turns dates and amounts back into dates and numbers. A value that cannot be represented exactly is refused by name rather than silently rounded.

The connection is read-only. An application cannot create, change or delete records in SAP through this vendor, and a declaration that tries is refused when the application starts.

Example — Show business partners beside project work

A project-tracking tool copies business partners from SAP every hour so a project can be attached to a customer and annotated by the team. The partner’s name and identifiers stay SAP’s: they are shown read-only and refreshed on the next pass. The team’s notes belong to the application and are never sent back.

For engineers

An instance names the vendor and the OData version of one service root. SAP’s classic Gateway services (/sap/opu/odata/sap/<SERVICE>) are version 2; its newer roots (/sap/opu/odata4/…) are version 4. A design example, in wildo.saas.config.ts:

systemsOfRecord: {
  'sap-bp': {
    vendor: 'sap-s4hana',
    dialect: HttpApiTransportDialect.ODATA_V2,
    credential: { kind: SystemOfRecordCredentialKind.BASIC },
  },
},

The facet offers two credential kinds for a communication user, HTTP Basic and a client certificate, so the instance names one; an instance that names neither is refused at startup. With Basic, each environment supplies the service root and the user name in wildo.infra.<env>.config.ts and the password in secrets.json; the variable names are derived from the instance name (WILDO_SOR_SAP_BP__BASE_URL, WILDO_SOR_SAP_BP__USERNAME, WILDO_SOR_SAP_BP__PASSWORD). A user name containing : is refused at startup, because Basic authentication uses the colon as its separator.

With MTLS_CLIENT_CERTIFICATE, the certificate goes in wildo.infra.<env>.config.ts and its private key in secrets.json (WILDO_SOR_SAP_BP__CERTIFICATE_PEM, WILDO_SOR_SAP_BP__PRIVATE_KEY_PEM), plus a CA bundle only when SAP’s server certificate is not one the platform already trusts. The certificate is presented in the TLS handshake, so the service root must be https:. An expired certificate, an encrypted key or a key that is not the certificate’s is refused at startup, and wildo config validate warns thirty days before the certificate expires.

A system reached through both versions is two instances, one per root, each with its own maxRequestsPerMinute. The two allowances are paced separately, so their sum is what must stay under what SAP grants the integration.

A binding’s dialect must match its instance’s. A version 2 root declared as ODATA is refused at the first read with a message naming the version, rather than failing on an unfamiliar response.

Copy a version 2 entity set

The following pipeline declaration is taken from the engine’s own version 2 integration test, which runs it against the SAP double on MongoDB and PostgreSQL. It reads A_BusinessPartner and maps three properties; the destination schema and registration are omitted.

const sapDeclaration = (): ExternalDataPipelineDeclaration => ExternalDataPipelineDeclarationSchema.parse({
  binding: {
    dialect: HttpApiTransportDialect.ODATA_V2, system: 'sap', entityRef: 'A_BusinessPartner',
    keyFields: [{ localField: 'id', remoteField: 'BusinessPartner', codec: HttpApiKeyComponentCodec.STRING }],
    subjectRights: { posture: SystemOfRecordSubjectRightsPosture.NO_SUBJECT_DATA },
  },
  remoteKeyLocalField: 'remoteKey', population: ExternalDataDestinationPopulation.CLOSED, orphanPolicy: ExternalDataOrphanPolicy.DELETE,
  maxRowsPerRun: 3,
  mapping: [
    { kind: ExternalDataMappingEntryKind.REMOTE_FIELD, remoteField: 'BusinessPartnerFullName', localField: 'name', required: true },
    { kind: ExternalDataMappingEntryKind.REMOTE_FIELD, remoteField: 'CreationDate', localField: 'createdOn', required: true },
    { kind: ExternalDataMappingEntryKind.REMOTE_FIELD, remoteField: 'CreditLimitAmount', localField: 'creditLimit', required: true },
  ],
});

The pass walks the entity’s key (BusinessPartner gt '<last key>', ordered by key) instead of following the service’s __next link, because version 2 promises no stability across a partial listing. A row inserted or deleted while a pass is under way is therefore neither read twice nor skipped. CreationDate lands as a date and CreditLimitAmount as a number. A value no number can hold exactly rejects that one row, and the rest of the pass continues; the same value in a live read refuses the read, naming the property.

The test’s subject-rights posture describes its fixture. A business partner can be a person, so a real binding declares its fields’ personal-data categories and keeps the default posture, under which SAP answers for its own records; the framework refuses NO_SUBJECT_DATA beside a field declared as personal data.

Read only what changed

On a version 4 root, a pipeline can read only the rows changed since its last pass, through an OData $filter … ge on a timestamp property the pipeline declaration names. Whether an entity carries such a property is for the declaration to know. A version 4 pipeline can also re-read records by key when an inbound notification names them. Version 2 roots run full passes, each walked by key.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.