Skip to Content

Introduction

This blog post is meant to provide additional information and quirks of the SAP Hybris Marketing OData v2.0 API set which can be used against Hybris Marketing 1709 onward. Please see the referenced documents as they provide detailed technical information on enabling these services and prerequisites for the services.

I will focus on importing Contact and Interaction data which make up the concept Lead in Hybris Marketing. I am using the POST $batch services, this blog documents the experience of using SAP Hybris Marketing OData $batch services.  The legacy service for reference is CUAN_IMPORT_SRV

The OData v2.0 Interactions API is used to log that “something happened” for a given contact. Some examples may include a social post from Twitter/Facebook, clicks on an ECommerce site, click through emails and more. More details on the API can be found here: https://help.sap.com/viewer/0f9408e4921e4ba3bb4a7a1f75f837a7/1805.500/en-US/6322d15482854566a72f6c9f1ab6cd1e.html

 

Table Of Contents

  • SAP OData v2.0 Overview
  • References
  • Prerequesites
  • $Batch OData background
  • $Batch OData on SAP
  • Create a Contact (API_MKT_CONTACT_SRV)
  • Create a new Interaction association to created contact (API_MKT_INTERACTION_SRV)
  • Challenges Identifying POST Payloads
  • Variable/Unexpected HTTP response code behavior

 

SAP Odata V2.0 API Broad Experience

Beginning in 2016, SAP began releasing new sets of OData API’s for their cloud services. I’ve had the pleasure of working with the Hybris Cloud for Customer, Hybris Marketing, and S/4HANA Cloud API’s

These API’s are abstract API wrappers that hit underlying SAP API services. See the below architecture diagram which gives context to the service layering and extensibility for adding custom services.

 

Advantages of OData v2.0 set include:

  • RESTy interface
  • Additionally functionality such as $batch support (previously unavailable for yMarketing document import)
  • Ability to PATCH data (previously unavailable), and better error logging around the service (previously less descriptive).
  • SAP integrators new to SAP may have an easier time working with abstract, limited API sets. It’s much more clear what data objects are being worked against, and allows new SAP integrators to pick up the services quickly and with less functional knowledge.

Considerations/drawbacks:

  • Availability of the documentation
  • Completeness of the documentation
  • Filling SAP functional gaps (less documented than older API services, JCO, ect.)
  • HTTP response codes are inconsistent and thus not very useful or informative
  • Complex syntax, strict field values, limited API discovery.

 

I hope my experiences may save some time and offer additional information for your research.

References:

The following are documents I came across during research and may be helpful to you:

Prerequesites

$Batch Ddata Background

OData $batch allows users to pass a single POST payload containing multiple underlying API calls. This is useful for chaining operations, operations that have dependent records which must be created first or importing very large record sets.

$batch uses Content-Type multipart/mixed. This allows for multiple calls to be sent but presents additional challenges to users unfamiliar with OdataV4

$Batch OData on SAP OData V2.0 API’s

I struggled to understand the error messaging. So far, I have built the following understanding for these API’s

  • Payloads POST to SAP Hybris are validated for invalid field values prior to $batch processing, if a problem is found an error is returned.
  • Not all fields are validated so I often receive 200 even though the call fails
    • Some pick-lists return errors, others do not.
  • Note API’s are SAP OData Version 2.0, however, it follows OData v4.0 Standards.https://www.odata.org/documentation/

 

Create a Contact (API_MKT_CONTACT_SRV)

First, we will successfully POST a Contact. I will use Postman to test the services. SAP uses header property x-csrf-token to prevent cross-site forgery attacks. You’ll need to Fetch this with a GET and pass it on subsequent POST requests.

Step 1: Retrieve the Cross Site Forgery Attack Token

  • Create a GET request with Basic Authentication (any Odata service turned on works)

 

  • Set the “x-csrf-token” parameter to “Fetch”. This retrieves our token for POST requests

 

  • Send the request, and copy “x-csrf-token” value from the response header

Step 2: POST a new Contact

  • POST Contact: https://{yMarketURL}/sap/opu/odata/sap/API_MKT_CONTACT_SRV/$batch
    • Enter the URL and set the method to POST
    • Ensure your Authorization header is present
    • Paste the “x-csrf-token” value retrieved in Step 1 to your POST request header
    • Set “Content-Type” to multipart/mixed
    • Set the Boundary (This tells the $batch OData service where to start/stop looking for your requests) **Very important for successful call**
    • White Space is important to maintain

 

  • Your API call configuration should look like this, note the URL, 3 headers, and Content-Type:
  • In the Body section, select “Raw” with type Text

 

  • Here is a sample POST payload ** Note your payload may require different values for proper creation. We will set the ContactID in this case to the Contacts email.
  • Note the underlying API “ContactOriginData” can be called directly instead of $batch
    --batch
    Content-Type: multipart/mixed; boundary=changeset_761e49b6-3146-4a57-8d10-15816fb9c75a
    
    --changeset_761e49b6-3146-4a57-8d10-15816fb9c75a
    content-type: application/http
    content-transfer-encoding: binary
    
    PUT ContactOriginData(ContactID='kellytg@cloud-elements.com',ContactOrigin='HOOTSUITE_ID') HTTP/1.1
    Content-Length: 1035
    Accept: application/json
    Sap-Cuan-SourceSystemType: EXT
    Sap-Cuan-SourceSystemId: HYBRIS
    Content-Type: application/json
    
    {
    "OriginDataLastChgUTCDateTime" : "2018-04-25T08:16:53",
     "CityName" : "Kiel",
     "Country" : "DE",
     "EmailAddress" : "kellytg@cloud-elements.com",
     "FirstName" : "Kelly",
     "LastName" : "Gold",
     "FullName" : "Kelly Gold",
     "GenderCode" : "1",
     "AddressHouseNumber" : "1",
     "IsConsumer" : false,
     "IsContactPerson" : true,
     "Language" : "DE",
     "MaritalStatus" : "2",
     "MaritalStatusName" : "Married",
     "MobileNumber" : "+49119201412191",
     "IsObsolete" : false,
     "PhoneNumber" : "+49115",
     "ContactPostalCode" : "24105",
      "StreetName" : "Hauptstrasse",
     "GenderCodeName" : "Mr."
    }
    
    
    --changeset_761e49b6-3146-4a57-8d10-15816fb9c75a--
    --batch--

 

  • Response: Note that no actual data is returned from this endpoint. 204 No Content
    --C2FB4429AD2DA1A89BA2589522671B620
    Content-Type: multipart/mixed; boundary=C2FB4429AD2DA1A89BA2589522671B621
    Content-Length:       411
    
    --C2FB4429AD2DA1A89BA2589522671B621
    Content-Type: application/http
    Content-Length: 243
    content-transfer-encoding: binary
    
    HTTP/1.1 204 No Content
    Content-Length: 0
    dataserviceversion: 2.0
    sap-message: {"code":"HPA_STAGING_AREA/037","message":"Payload is processed via staging area; check results in Import Monitor","severity":"info","target":"","details":[]}
    
    
    --C2FB4429AD2DA1A89BA2589522671B621--
    
    --C2FB4429AD2DA1A89BA2589522671B620--
    

Step 3: Verify your contact has been created via API or UI.

Create a new Interaction associated with an existing contact (API_MKT_INTERACTION_SRV)

This is where it got tricky, by in large part, the interaction endpoint will return a 20x code. However in my experience the success of the request heavily depends on your fields, and the error messaging could be improved.

  • Retrieve the x-csrf-token if you haven’t already (see previous step)
  • https://{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/$batch
  • Your request configuration should look like…
  • Here is a sample POST payload
    --batch
    content-type:multipart/mixed; boundary=changeset_761e49b6-3146-4a57-8d10-15816fb9c75a
    
    --changeset_761e49b6-3146-4a57-8d10-15816fb9c75a
    content-type: application/http
    content-transfer-encoding: binary
    
    POST Interactions HTTP/1.1
    Content-Length: 1035
    Accept: application/json
    Content-Type: application/json
    
    {
    "InteractionContactOrigin":"EMAIL",
    "InteractionContactId":"kellytg@cloud-elements.com",
    "CommunicationMedium":"ONLINE_SHOP",
    "InteractionType":"SHOP_CART_VIEW",
    "InteractionTimeStampUTC":"2018-04-25T08:16:53",
    "InteractionSourceObjectType":"COMMERCE_SC",
    "InteractionSourceObject":"4444",
    "MarketingArea":"CXXGLOBAL",
    "CampaignID":"12121212",
    "InteractionLanguage":"EN",
    "InteractionAmount":"12.34",
    "InteractionCurrency":"EUR",
    "InteractionLatitude":"49.304864",
    "InteractionLongitude":"8.641526",
    "SpatialReferenceSystem":"",
    "DeviceType":"",
    "InteractionDeviceName":"",
    "SourceSystemType":"COMMERCE"
    }
    
    
    
    --changeset_761e49b6-3146-4a57-8d10-15816fb9c75a--
    
    --batch--

 

  • The response should be “201 Created” and return some data inline. Note data in-line cannot be resolved to a record. The GUID is generic, always the same,  and invalid. “0000-0000-000001” 
    --AA0B252A8DA9C5F5D557E2A499E513FA0
    Content-Type: multipart/mixed; boundary=AA0B252A8DA9C5F5D557E2A499E513FA1
    Content-Length:      3591
    
    --AA0B252A8DA9C5F5D557E2A499E513FA1
    Content-Type: application/http
    Content-Length: 3422
    content-transfer-encoding: binary
    
    HTTP/1.1 201 Created
    Content-Type: application/json
    Content-Length: 3168
    location: https://{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')
    dataserviceversion: 2.0
    
    {"d":{"__metadata":{"id":"https://{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')","uri":"https://{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')","type":"API_MKT_INTERACTION_SRV.Interaction"},"InteractionUUID":"00000000-0000-0000-0000-000000000001","InteractionContactOrigin":"","InteractionContactId":"","CommunicationMedium":"","InteractionType":"","InteractionTimeStampUTC":null,"InteractionSourceObjectType":"","InteractionSourceObject":"","MarketingArea":"","CampaignID":"","MarketingLocationOrigin":"","MarketingLocation":"","DigitalAccountType":"","DigitalAccount":"","MKT_AgreementOrigin":"","MKT_AgreementExternalID":"","CampaignContent":0,"InteractionWeightingFactor":0,"InteractionSentimentValue":0,"InteractionStatus":"","InteractionReason":"","InteractionLanguage":"","InteractionIsAnonymous":false,"InteractionAmount":"0.00000000000000","InteractionCurrency":"","InteractionLatitude":"0.0000000000","InteractionLongitude":"0.0000000000","SpatialReferenceSystem":"","DeviceType":"","InteractionDeviceName":"","PrecedingInteractionUUID":"00000000-0000-0000-0000-000000000000","SourceSystemType":"","SourceSystem":"","InteractionSourceObjectAddlID":"","InteractionSourceObjectStatus":"","InteractionSourceDataURL":"","InteractionSourceTimeStampUTC":null,"CampaignContentLinkURL":"","CampaignContentLinkName":"","InteractionContactUUID":"00000000-0000-0000-0000-000000000000","InteractionLastChangedDateTime":null,"InteractionLastChangedByUser":"","InteractionContentSubject":"","InteractionContent":"","InteractionInterests":{"__deferred":{"uri":"{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionInterests"}},"InteractionProductCategories":{"__deferred":{"uri":"{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionProductCategories"}},"InteractionDigitalAssets":{"__deferred":{"uri":"https://{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionDigitalAssets"}},"InteractionOffers":{"__deferred":{"uri":"https://{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionOffers"}},"InteractionAdditionalObjects":{"__deferred":{"uri":"https://{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionAdditionalObjects"}},"InteractionProducts":{"__deferred":{"uri":"https://{yMarketURL}.s4hana.ondemand.com/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionProducts"}},"InteractionTags":{"__deferred":{"uri":"https://{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionTags"}}}}
    --AA0B252A8DA9C5F5D557E2A499E513FA1--
    
    --AA0B252A8DA9C5F5D557E2A499E513FA0--
    

 

  • Note that our Interaction has been created for our contact “kellytg@cloud-elements.com”

 

Challenges Identifying POST Payloads.

With each of the 3 new OData API sets I have used (S/4HANA Cloud, Hybris Marketing, Cloud 4 Customer) the main challenge is properly passing field values. Failure to pass the correct field values will often result in a silent (successful response) error.

These errors range in HTTP code, (200, 400, 500) so the only real feedback I get as a user is “my payload was wrong” and I must keep trying until something works.

There are some particular items that would be helpful in resolving these issues.

  • API Structure for given object
  • Required fields for given object
  • Discovery of table values for pick list fields
  • Improved documentation on acceptable values for API endpoints.

SAP has been improving the Hybris Marketing Integration Guide, but each guide seems to differ slightly. I find I am bouncing between any release between 1709 and 1808 to find all available information.

They have also started providing sample payloads in SAP help (integration guide) SAP Marketing Cloud 1808. These payloads are a good place to start, but for association of records serve only as a starting point. You will need to validate that your values match up with your tables respectively (Campaign ID’s, role ID’s ect)

The metadata services allow you to learn the names of the services available but I’ve not found a way to discover more about how to use the API’s. Sap GUI is a good tool to access the DB tables

Since SAP is still rolling out sample code as of Aug 2018, my recommendation is to identify the relevant underlying table, access the table directly and look for accepted values.

 

Variable/Unexpected HTTP response behavior

Perhaps the item that contributed to my slowdown most across these three services is the variable and unexpected HTTP response codes. Here are a few key points/observations which I am sharing in hopes to save some others time.

  • SAP catches some errors when pick-list values do not match. However it does not catch all fields, and many fields with only a few acceptable values will return 20x when passed.
  • HTTP code does not indicate success of POST
    • POST /interactions almost always returns 201, but in fact, often the Interaction is not created.
  • POST /InteractionContact (not discussed in this blog) returns 204 No Content but no Contact was created
  • Successful POST /Interactions returns dummy data (GUID does not resolve to the record created)

To show this behavior of successful HTTP code but failure to POST. See below:

  • Same payload from step “Create a new Interaction associated with an existing contact (API_MKT_INTERACTION_SRV)”
  • I’ve intentionally put typos in many of my fields. I added 1 day to InteractionTimeStampUTC
  • None of these fields cause the API to return an error, try changing IntractionLanguage. It seems to primarily validate against data type. 
    --batch
    content-type:multipart/mixed; boundary=changeset_761e49b6-3146-4a57-8d10-15816fb9c75a
    
    --changeset_761e49b6-3146-4a57-8d10-15816fb9c75a
    content-type: application/http
    content-transfer-encoding: binary
    
    POST Interactions HTTP/1.1
    Content-Length: 1035
    Accept: application/json
    Content-Type: application/json
    
    {
    "InteractionContactOrigin":"EMDAIL",
    "InteractionContactId":"kellytg@cloud-elements.com",
    "CommunicationMedium":"ONLIDDDNE_SHOPdd",
    "InteractionType":"SHOP_CART_VIsdddEW",
    "InteractionTimeStampUTC":"2018-05-26T08:16:53",
    "InteractionSourceObjectType":"COMMfdERCE_SC",
    "InteractionSourceObject":"4444",
    "MarketingArea":"CXXGLsdcOBAL",
    "CampaignID":"12121212",
    "InteractionLanguage":"EN",
    "InteractionAmount":"12.34",
    "InteractionCurrency":"EUR",
    "InteractionLatitude":"49.304864",
    "InteractionLongitude":"8.641526",
    "SpatialReferenceSystem":"",
    "DeviceType":"",
    "InteractionDeviceName":"",
    "SourceSystemType":"COMMEasdfRCE"
    }
    
    
    
    --changeset_761e49b6-3146-4a57-8d10-15816fb9c75a--
    
    --batch--

 

  • Note the API returns a 201 Created HTTP code and the same dummy data (GUID 000-000-00001)
    --FA9C1091B0FAC6861914EBBC6FA085730
    Content-Type: multipart/mixed; boundary=FA9C1091B0FAC6861914EBBC6FA085731
    Content-Length:      3591
    
    --FA9C1091B0FAC6861914EBBC6FA085731
    Content-Type: application/http
    Content-Length: 3422
    content-transfer-encoding: binary
    
    HTTP/1.1 201 Created
    Content-Type: application/json
    Content-Length: 3168
    location: https://{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')
    dataserviceversion: 2.0
    
    {"d":{"__metadata":{"id":"{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')","uri":"https://{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')","type":"API_MKT_INTERACTION_SRV.Interaction"},"InteractionUUID":"00000000-0000-0000-0000-000000000001","InteractionContactOrigin":"","InteractionContactId":"","CommunicationMedium":"","InteractionType":"","InteractionTimeStampUTC":null,"InteractionSourceObjectType":"","InteractionSourceObject":"","MarketingArea":"","CampaignID":"","MarketingLocationOrigin":"","MarketingLocation":"","DigitalAccountType":"","DigitalAccount":"","MKT_AgreementOrigin":"","MKT_AgreementExternalID":"","CampaignContent":0,"InteractionWeightingFactor":0,"InteractionSentimentValue":0,"InteractionStatus":"","InteractionReason":"","InteractionLanguage":"","InteractionIsAnonymous":false,"InteractionAmount":"0.00000000000000","InteractionCurrency":"","InteractionLatitude":"0.0000000000","InteractionLongitude":"0.0000000000","SpatialReferenceSystem":"","DeviceType":"","InteractionDeviceName":"","PrecedingInteractionUUID":"00000000-0000-0000-0000-000000000000","SourceSystemType":"","SourceSystem":"","InteractionSourceObjectAddlID":"","InteractionSourceObjectStatus":"","InteractionSourceDataURL":"","InteractionSourceTimeStampUTC":null,"CampaignContentLinkURL":"","CampaignContentLinkName":"","InteractionContactUUID":"00000000-0000-0000-0000-000000000000","InteractionLastChangedDateTime":null,"InteractionLastChangedByUser":"","InteractionContentSubject":"","InteractionContent":"","InteractionInterests":{"__deferred":{"uri":"https://{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionInterests"}},"InteractionProductCategories":{"__deferred":{"uri":"{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionProductCategories"}},"InteractionDigitalAssets":{"__deferred":{"uri":"{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionDigitalAssets"}},"InteractionOffers":{"__deferred":{"uri":"https://{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionOffers"}},"InteractionAdditionalObjects":{"__deferred":{"uri":"https://{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionAdditionalObjects"}},"InteractionProducts":{"__deferred":{"uri":"{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionProducts"}},"InteractionTags":{"__deferred":{"uri":"{yMarketURL}/sap/opu/odata/sap/API_MKT_INTERACTION_SRV/Interactions(guid'00000000-0000-0000-0000-000000000001')/InteractionTags"}}}}
    --FA9C1091B0FAC6861914EBBC6FA085731--
    
    --FA9C1091B0FAC6861914EBBC6FA085730--
    
  • Observe no record has been created. 

Wrap Up

I am very pleased with the new API sets from SAP, they offer very powerful abstract functionality which has previously not been available in such a consumable format. That said, there are some pretty major quirks in identifying fields, and debugging as the API response behavior is often not indicative of the error I am making.

The new Hybris API’s are easier to use than the legacy services, but there is much more documentation for older services like CUAN_IMPORT_SRV. As the API’s mature this gap in documentation should round out and these API’s will be a great choice where the use-case allows.

I hope this post can provide some useful information to you,

 

Happy Trails

To report this post you need to login first.

1 Comment

You must be Logged on to comment or reply to a post.

  1. Jay Malla

    Hi Kelly,

    Excellent BLOG with great attention to detail!  Keep them coming.  This definitely will help users test the Hybris OData interfaces.  Maybe a follow up BLOG on debugging the calls through SAP Netweaver Gateway with external debugging set, will be good way to see why sometimes the calls are failing but the response does not show the descriptive error messages.  With a lot of the OData calls, it is helpful to debug the underlying ABAP code – often using the Gateway client.

    Nice work!

    Regards,

    Jay

    (1) 

Leave a Reply