SAP S/4HANA and ECC

Gateway service roots, OData V2 and V4 paths, the sap-client parameter and the service catalog.

S/4HANA and ECC publish their APIs through SAP Gateway - classic services speak OData V2 and newer S/4HANA ones speak V4. Both kinds work with outgoing SAP connections, the only difference being the address and the version selected in the connection's form.

Connection settings

  • Address: https://host:port/sap/opu/odata/sap/API_BUSINESS_PARTNER (V2) or https://host:port/sap/opu/odata4/sap/... (V4)
  • OData version: match the service - 2.0 for /sap/opu/odata/, 4.0 for /sap/opu/odata4/
  • Auth type: Basic with a technical user, or OAuth2 in BTP-fronted setups
  • Needs CSRF token: keep it enabled - Gateway requires X-CSRF-Token for every write

Finding services

Every Gateway system holds a catalog of the services it activates. Reading it through an outgoing SAP connection pointed at the catalog's root lists what is available:

  • Catalog address: https://host:port/sap/opu/odata/IWFND/CATALOGSERVICE;v=2
  • Entity set: ServiceCollection
conn = self.sap['SAP.Catalog']

for service in conn.iter('ServiceCollection', top=50):
    self.logger.info('Service -> %s', service['TechnicalServiceName'])

In S/4HANA, the services themselves are documented in the SAP API Business Hub - the entity sets used in these chapters, such as A_BusinessPartner in API_BUSINESS_PARTNER, come from the standard whitelisted APIs.

The sap-client parameter

Systems with more than one client expect the sap-client query parameter with each request. It travels through the custom query option, alongside any other options:

partners = conn.read('A_BusinessPartner',
    top=10,
    custom={'sap-client': '100'},
)

Gateway services model relations as navigation properties - addresses of a business partner, items of a sales order. The expand option brings them inline with the parent:

orders = conn.read('A_SalesOrder',
    filter="SalesOrganization eq '1010'",
    expand='to_Item',
    top=10,
)

for order in orders:
    items = order['to_Item']
    self.logger.info('Order %s has %d items', order['SalesOrder'], len(items))

Learn more