Skip navigation

Code It

8 Posts authored by: Lou Patrick

The Eloqua Release 19B New Feature Summary is now available - Eloqua Release 19B New Feature Summary.

 

Here is an extract of all the Developer Updates:

 

NEW FEATURES

 

Application API

  • Asynchronous form processing via API is released under our Controlled Availability program.

IMPORTANT: You can request access to this feature by submitting a request to My Oracle Support. A future Product Notice will provide details.

  • A new property has been added to the Application 1.0 and 2.0 Custom Objects API endpoints. The deleteLinkedCustomObjectData property is a boolean that determines whether or not custom object records are deleted when linked contact records are deleted. Does not apply to records that are unmapped or account deletion. Deleting records is irreversible and data cannot be recovered. The default value is false.

IMPORTANT: This feature is released under our Controlled Availability program. You can request access to this feature by submitting a request to My Oracle Support.

 

Bulk API

  • We’ve added two new activity fields: WebVisitSavedId and PageViewSavedId. WebVisitSavedId is available when creating an activity export definition for web visit activity types to include the id of when web visits were saved. PageViewSavedId is available when creating an activity export definition for page view activity types to include the id of when page views were saved. You can use the new activity fields in the activity export definition filter to successfully export web visit and page view activities periodically without missing any.

 

The syntax for the new activity fields:

               WebVisitSavedId: {{Activity.Field(WebVisitSavedId)}}

               PageViewSavedId: {{Activity.Field(PageViewSavedId)}

 

When export data is retrieved, the WebVisitSavedId will be populated with the unique identifier for when the web visit was saved, and PageViewSavedId will be populated with the unique identifier for when the page view was saved.

 

PLATFORM NOTICES

 

  • As of release 19C, all API endpoints for emails and landing pages will not allow creating and editing classic emails and landing pages. Classic forms will still be allowed to be edited through release 20A, but no new classic forms will be allowed to be created starting in 19C. Any forms created or updated with the API endpoints will need to be responsive starting in 19C. A future product notice will provide details on usage. For more information about the classic editor sunset, see the Email and Landing Page product notice and the Form product notice.
  • With the arrival of Eloqua release 19C (Aug 2019), Oracle will be modifying its supported cipher suites used for Transport Layer Security (TLS) connections to Eloqua. This includes programmatic access to Eloqua via APIs. Learn more

 

RECENT CHANGES

 

Application API

  • As part of the Classic Design Editor Sunset, the API endpoints will not allow you to create classic emails and landing pages (request body for htmlContent with a type of StructuredHtmlContent). However, you can still use the API to edit emails and landing pages. Any new emails and landing pages created using the API endpoints should have a request body for htmlContentwith a type of RawHtmlContent. If this is not specified, the API endpoints will still default to creating new emails and landing pages with RawHtmlContent. Creating emails or landing pages with the htmlContent type ResponsiveHtmlContent is not supported. If you use the ResponsiveHtmlContent type, we cannot guarantee your email or landing page will be responsive. As of release 19C, all API endpoints for emails and landing pages will not allow creating and editing classic emails and landing pages. Classic forms will still be allowed to be edited through release 20A, but no new classic forms will be allowed to be created starting in 19C. Any forms created or updated with the API endpoints will need to be responsive starting in 19C. A future product notice will provide details on usage. For more information about the classic editor sunset, see the Email and Landing Page product notice and the Form product notice.
  • A new property, isHidden, that stores the “Form Visibility” form setting, was added to the Application API Form endpoints. For more information, see the product notice.

 

Bulk API

  • Resolved an issue where performing an activity export for Subscribe, Unsubscribe, and Bounceback activity types using the statement {{Activity.Field(EmailAddress)}} could result in email addresses being returned in a case different than the case set on the contact currently. Performing an activity export for these activity types, and using this statement, will now return email addresses in the current case of the contact.
  • Resolved an issue where creating an activity import sync and then syncing invalid data to the import definition would result in the sync status being active instead of error.
  • Optimizations have been made to Bulk API custom object imports that include a large number of fields and records.
  • Bulk API Contact export definitions with an EXISTS() Segment filter, e.g. EXISTS('{{ContactSegment[<id>]}}'), will only allow up to two system updated or created date criterion. For more information, see the product notice.

The Eloqua Release 19A New Feature Summary is now available - Eloqua Release 19A New Feature Summary.

 

Here is an extract of all the Developer Updates:

 

NEW FEATURES

 

Application API

  • Added a new Audit Log API endpoint to initiate an Eloqua audit log export using the Application API. This API endpoint enables you to specify the type of audit log you want to export within a specified date and time range. If the request is successful, a report will be sent to the email address specified in the request. Previously, initiating audit log exports were only accessible in Eloqua or using an SFTP server after exporting.
  • A new property has been added to the Contact Fields API endpoints. The showTrustedVisitorsOnly property is a boolean that determines whether or not a contact field is displayed only to trusted visitors. A trusted visitor is a contact who has undergone an additional form of verification. A contact field where this property is set to true will only be displayed to trusted visitors.

 

Bulk API

  • We've added a new activity field that enables you to filter on the date and time a visitor was linked to a contact. This activity field is available when creating an activity export definition for page view or web visit activity types. Activity records are only returned when linked to a contact. Previously, to export all newly linked activity records, an export as far back as an activity is meaningful would need to be performed to capture newly linked records. With LinkedToContactDate, newly linked activity records can efficiently be exported without exporting activities that have already been exported. The syntax for the new LinkedToContactDate activity field is:
    {{Activity.Field(LinkedToContactDate)}}

       When export data is retrieved, the LinkedToContactDate will be populated with the date and time the contact was linked to a visitor.

PLATFORM NOTICES

 

The API endpoints for emails, landing pages, and forms are undergoing changes in updates 19B and 19C as part of the Classic Design Editor Sunset. As of update 19B, the API endpoints will not allow you to create classic emails and landing pages (request body for htmlContentwith a type of StructuredHtmlContent). However, you can still use the API to edit emails and landing pages. Any new emails and landing pages created using the API endpoints should have a request body for htmlContent with a type of RawHtmlContent. Creating emails or landing pages with the htmlContent type ResponsiveHtmlContent is not supported. If you use the ResponsiveHtmlContent type, we cannot guarantee your email or landing page will be responsive. For the forms API endpoints, you will still be able to use the API to create and update classic forms. As of update 19C, all API endpoints will not allow creating and editing classic emails, landing pages, or forms. Any forms created with the API endpoints will need to be responsive. A future Product Notice will provide details on usage. For more information about the classic editor sunset, see the product notice.

RECENT CHANGES

 

  • Oracle Eloqua deprecated TLS 1.1 on January 18, 2019. Learn more

 

Application API

  • Resolved an issue with the Application 2.0 Custom Object API endpoints where retrieving and updating a custom object with a custom object field name greater than 50 characters would truncate the name value to 50 characters in the response. With update 19A, if the custom object field name is greater than 50 characters, there will be a validation error.
  • The API endpoint to retrieve a list of activities will now only return EmailContent when depth is set to complete. The default depth for this endpoint is complete, and remains unchanged. Set the depth to minimal in the request URL to have EmailContent not returned.
  • Resolved an issue where using the Application 1.0 Accounts API endpoint to find matching accounts based on a search parameter would not return all of the account fields in the response.
  • The ability to mark forms as internal so that form submissions are dropped, is now available in Controlled Availability. This feature includes the addition of a new property to the Form API endpoints. You can request access to this feature by submitting a request to My Oracle Support.

 

Bulk API

  • Resolved an issue where creating an activity export definition with ReferrerUrl in the filter would cause the export sync to fail. With update 19A, a validation error will be returned if trying to use ReferrerUrl in the filter of a page view export definition.
  • The Campaign Response endpoints are now Generally Available. These endpoints enable developers to access campaign responses, to support the specific use case of updating a campaign response status in CRM via an App on Program or Campaign Canvas. These endpoints can only be used by an App service instance selected in Response Rules setup, and cannot be accessed otherwise. With this feature, the Campaign Responses Data Source for listeners will also be Generally Available.
  • Resolved an issue where a Bulk API import that did not include C_EmailAddress within fields and isUpdatingMultipleMatchedRecords was set to true, did not successfully update matching records.

DOCUMENTATION ENHANCEMENTS OF NOTE

 

We have added a new tutorial that explains how to use the Campaign API endpoints to create a detailed campaign with steps.

 

There is a new campaign element reference page. This topic lists the JSON objects needed to create campaign steps when creating campaigns using the Campaigns API endpoints.

The Eloqua Release 18D New Feature Summary is now available - Eloqua Release 18D New Feature Summary.

 

Here is an exert of all the Developer Updates:

NEW FEATURES

 

Application API

  • You can now use the Landing Pages Application API 2.0 endpoints to create, update, retrieve, and delete landing pages.
  • You can now use the Forms Application API 2.0 endpoints to create, update, retrieve, and delete forms.
  • You can now use the Form Data Application API 2.0 endpoints to create, retrieve, and delete form data.

 

PLATFORM NOTICES

 

App Developer Framework

  • There is now a Scheduling option available for app action steps on Campaign and Program Canvases. If a user sets certain days and hours under Scheduling, records that enter the step outside of the selected days and hours, will remain in an “Awaiting action” status until within the selected days and hours, at which time the Notification URL call will then be sent to the app.
  • The ability to mark forms as internal so that form submissions are dropped, will be in Controlled Availability in Eloqua Release 18D and 19A. This feature includes the addition of a new property to Form API endpoints. You can request access to this feature by submitting a request to My Oracle Support.
  • Oracle Eloqua will be deprecating TLS 1.1 on January 18, 2019. Learn more

 

RECENT CHANGES

 

Authentication

  • Resolved an issue where calls to Eloqua's OAuth 2.0 token endpoint would return an empty response body if there was an error with the request. Error requests made to Eloqua's OAuth 2.0 token endpoint will now return a detailed message. The token endpoint is https://login.eloqua.com/auth/oauth2/token.
  • We modified the Eloqua OAuth 2.0 authorization flow initiation to only accept one initiation per minute for any given user of an app. For more information, see the product notice on Topliners.

 

Application API

  • The processingStepErrors property has been added to the response to submitting a form using the Application 1.0 and 2.0endpoints. This property was previously in Early Preview status in Release 18C. This property provides more detailed information about form processing step errors. For more information, see the product notice on Topliners.
  • A new form field validation property has been added to the Application 2.0 Form endpoints called PreventXSSCondition. This field validation is being added to prevent form data from being saved if HTML is present in a field. For more information, see the product notice on Topliners.
  • The requirement property has been added to the response when submitting form data with invalid values using the Application 1.0 and 2.0 endpoints. This property provides more information about invalid form data being submitted to a form field. See the Eloqua Help Center for more information about form field validation.
  • Resolved an issue where a campaign import could modify the end date of a campaign that is Completed or Active, causing campaigns to end before the intended end date. Campaign imports will no longer be able to modify the end date of campaigns that are Completed or Active.
  • As mentioned in the Eloqua 18C changelog, the resendLimit property introduced in 18A was removed from the processingSteps type FormStepSendEmail for Application API Form endpoints. For more information, see the product notice on Topliners.
  • As mentioned in the Eloqua 18C changelog, the Create an external activity Application API endpoint will no longer create External Asset Types or External Activity Types if they do not exist. A new Action Permission, "Register External Activities", is required to use the Create an external activity Application API endpoint. Learn more

 

Bulk API

  • You can now include Contact.Id for Bounceback, Subscribe, and Unsubscribe activity export definitions. This enhancement enables developers to include Contact.Id on export definitions for all activity types.
  • You can now include user fields for EmailOpen, EmailClickthrough, and EmailSend activity export definitions. One use case this enhancement enables, is allowing including sender and user attributes on email activities exported via the Bulk API, so that the activity can be properly assigned to the correct user in CRM. Discover user fields using the Retrieve a list of user fields endpoint: GET /api/Bulk/2.0/users/fields. User fields can be added with the following statement: {{Activity.User.Field(<Field Name>)}}

With the Oracle Eloqua 18B release Opportunities are being added to the Bulk API. I am going to walk-through an end to end scenario with examples, but before I do that here are some of the highlights:

  • Opportunity fields will be discoverable via the Bulk API
  • There are two sets of endpoints, one set to import Opportunities, and the other set to link Opportunities to Contacts
  • Opportunities can be linked directly to Contacts, or via Accounts

 

With the key takeaways highlighted, let's now take a look at this new functionality in action. I'm going to walk-through discovering Opportunity fields, using those fields to import Opportunities, and completing the scenario by linking the Opportunities to Contacts. Along the way we'll cover some requirements and notes as well.

 

Let's start by retrieving Opportunity fields:

 

     Request

GET /api/Bulk/2.0/opportunities/fields

     Response

{

    "items": [

        {

            "name": "Opportunity ID",

            "internalName": "RemoteOpportunityID",

            "dataType": "string",

            "hasReadOnlyConstraint": false,

            "hasNotNullConstraint": true,

            "hasUniquenessConstraint": false,

            "statement": "{{Opportunity.Id}}",

            "uri": "/opportunities/fields/38"

        },

        {

            "name": "Opportunity Name",

            "internalName": "OpportunityName",

            "dataType": "string",

            "hasReadOnlyConstraint": false,

            "hasNotNullConstraint": true,

            "hasUniquenessConstraint": false,

            "statement": "{{Opportunity.Field(Name)}}",

            "uri": "/opportunities/fields/39"

        },

        {

            "name": "Created Date",

            "internalName": "CreatedDate",

            "dataType": "date",

            "hasReadOnlyConstraint": false,

            "hasNotNullConstraint": true,

            "hasUniquenessConstraint": false,

            "statement": "{{Opportunity.CreatedAt}}",

            "uri": "/opportunities/fields/40"

        },

        {

            "name": "Stage",

            "internalName": "OpportunityStageID",

            "dataType": "string",

            "hasReadOnlyConstraint": false,

            "hasNotNullConstraint": true,

            "hasUniquenessConstraint": false,

            "statement": "{{Opportunity.Field(Stage)}}",

            "uri": "/opportunities/fields/41"

        },

        {

            "name": "Amount",

            "internalName": "OpportunityAmount",

            "dataType": "number",

            "hasReadOnlyConstraint": false,

            "hasNotNullConstraint": true,

            "hasUniquenessConstraint": false,

            "statement": "{{Opportunity.Field(Amount)}}",

            "uri": "/opportunities/fields/42"

        },

        {

            "name": "Currency",

            "internalName": "OpportunityCurrencyID",

            "dataType": "string",

            "hasReadOnlyConstraint": false,

            "hasNotNullConstraint": false,

            "hasUniquenessConstraint": false,

            "statement": "{{Opportunity.Field(Currency)}}",

            "uri": "/opportunities/fields/43"

        },

        {

            "name": "Close Date",

            "internalName": "CloseDate",

            "dataType": "date",

            "hasReadOnlyConstraint": false,

            "hasNotNullConstraint": false,

            "hasUniquenessConstraint": false,

            "statement": "{{Opportunity.Field(CloseDate)}}",

            "uri": "/opportunities/fields/44"

        },

        {

            "name": "Forecast To Close Date",

            "internalName": "ForecastToCloseDate",

            "dataType": "date",

            "hasReadOnlyConstraint": false,

            "hasNotNullConstraint": false,

            "hasUniquenessConstraint": false,

            "statement": "{{Opportunity.Field(ForecastToCloseDate)}}",

            "uri": "/opportunities/fields/45"

        },

        {

            "name": "Account Name",

            "internalName": "AccountName",

            "dataType": "string",

            "hasReadOnlyConstraint": false,

            "hasNotNullConstraint": false,

            "hasUniquenessConstraint": false,

            "statement": "{{Opportunity.Field(AccountName)}}",

            "uri": "/opportunities/fields/46"

        },

        {

            "name": "Territory",

            "internalName": "Territory",

            "dataType": "string",

            "hasReadOnlyConstraint": false,

            "hasNotNullConstraint": false,

            "hasUniquenessConstraint": false,

            "statement": "{{Opportunity.Field(Territory)}}",

            "uri": "/opportunities/fields/47"

        }

    ],

    "totalResults": 10,

    "limit": 1000,

    "offset": 0,

    "count": 10,

    "hasMore": false

}

 

Now that we have the statements for the Opportunity fields, let's create an Opportunity import definition:

 

Before creating the definition, here are some requirements for the Opportunity import definition:

  • The following fields are required:
    • {{Opportunity.Id}}

    • {{Opportunity.Field(Name)}}

    • {{Opportunity.Field(Stage)}}

    • {{Opportunity.Field(Amount)}}

  • The identifierFieldName must be set to the {{Opportunity.Id}} field within fields

 

     Request

POST /api/Bulk/2.0/opportunities/imports

Content-Type: application/json

     Request - Body

{

  "name": "Opportunity Import",

  "fields": {

    "OpportunityID": "{{Opportunity.Id}}",

    "OpportunityName": "{{Opportunity.Field(Name)}}",

    "AccountName": "{{Opportunity.Field(AccountName)}}",

    "CreatedDate": "{{Opportunity.CreatedAt}}",

    "Amount": "{{Opportunity.Field(Amount)}}",

    "CloseDate": "{{Opportunity.Field(CloseDate)}}",

    "Currency": "{{Opportunity.Field(Currency)}}",

    "ForecastToCloseDate": "{{Opportunity.Field(ForecastToCloseDate)}}",

    "Stage": "{{Opportunity.Field(Stage)}}",

    "Territory": "{{Opportunity.Field(Territory)}}"

  },

  "identifierFieldName": "OpportunityID",

  "isIdentifierFieldCaseSensitive": false,

  "isSyncTriggeredOnImport": true

}

     Response

201 Created

{

    "isIdentifierFieldCaseSensitive": false,

    "name": "Opportunity Import",

    "fields": {

        "OpportunityID": "{{Opportunity.Id}}",

        "OpportunityName": "{{Opportunity.Field(Name)}}",

        "AccountName": "{{Opportunity.Field(AccountName)}}",

        "CreatedDate": "{{Opportunity.CreatedAt}}",

        "Amount": "{{Opportunity.Field(Amount)}}",

        "CloseDate": "{{Opportunity.Field(CloseDate)}}",

        "Currency": "{{Opportunity.Field(Currency)}}",

        "ForecastToCloseDate": "{{Opportunity.Field(ForecastToCloseDate)}}",

        "Stage": "{{Opportunity.Field(Stage)}}",

        "Territory": "{{Opportunity.Field(Territory)}}"

    },

    "identifierFieldName": "OpportunityID",

    "isSyncTriggeredOnImport": true,

    "dataRetentionDuration": "P7D",

    "uri": "/opportunities/imports/18819",

    "createdBy": "API.User",

    "createdAt": "2018-04-30T18:05:26.9420890Z",

    "updatedBy": "API.User",

    "updatedAt": "2018-04-30T18:05:26.9420890Z"

}

 

Now that we have our definition created we can upload data to the definition:

 

     Request

POST /api/Bulk/2.0/opportunities/imports/18819/data

Content-Type: application/json

     Request - Body

[

  {

    "OpportunityID": "1ABC",

    "OpportunityName": "ABC Company",

    "AccountName": "ABC",

    "CreatedDate": "2018-04-15 1:15",

    "Amount": "1000000",

    "CloseDate": "",

    "Currency": "USD",

    "ForecastToCloseDate": "",

    "Stage": "Prospecting",

    "Territory": "West"

  },

  {

    "OpportunityID": "2XYZ",

    "OpportunityName": "XYZ Company",

    "AccountName": "XYZ",

    "CreatedDate": "2018-04-18 5:10",

    "Amount": "1000000",

    "CloseDate": "",

    "Currency": "USD",

    "ForecastToCloseDate": "",

    "Stage": "Prospecting",

    "Territory": "West"

  }

]

     Response

201 Created

{

    "syncedInstanceUri": "/opportunities/imports/18819",

    "status": "pending",

    "createdAt": "2018-04-30T18:19:51.4928305Z",

    "createdBy": "EveryOne.Tests",

    "uri": "/syncs/27418"

}

As I knew I could upload all data in one request to the data endpoint, I set the isSyncTriggeredOnImport property to true, so the response to the data upload is the created sync. Now I'll retrieve the sync logs to confirm the Opportunities were created:

 

     Request

GET /api/Bulk/2.0/syncs/27418/logs

     Response

{

    "items": [

        {

            "syncUri": "/syncs/27418",

            "count": 2,

            "severity": "information",

            "statusCode": "ELQ-00130",

            "message": "Total records staged for import.",

            "createdAt": "2018-04-30T18:19:53.5400000Z"

        },

        {

            "syncUri": "/syncs/27418",

            "count": 0,

            "severity": "information",

            "statusCode": "ELQ-00137",

            "message": "Ready for data import processing.",

            "createdAt": "2018-04-30T18:19:53.5530000Z"

        },

        {

            "syncUri": "/syncs/27418",

            "count": 0,

            "severity": "information",

            "statusCode": "ELQ-00101",

            "message": "Sync processed for sync , resulting in Success status.",

            "createdAt": "2018-04-30T18:20:56.0730000Z"

        },

        {

            "syncUri": "/syncs/27418",

            "count": 2,

            "severity": "information",

            "statusCode": "ELQ-00001",

            "message": "Total records processed.",

            "createdAt": "2018-04-30T18:19:53.0800000Z"

        },

        {

            "syncUri": "/syncs/27418",

            "count": 2,

            "severity": "information",

            "statusCode": "ELQ-00041",

            "message": "Opportunities created.",

            "createdAt": "2018-04-30T18:19:57.0530000Z"

        },

        {

            "syncUri": "/syncs/27418",

            "count": 0,

            "severity": "information",

            "statusCode": "ELQ-00042",

            "message": "Opportunities updated.",

            "createdAt": "2018-04-30T18:19:57.0530000Z"

        }

    ],

    "totalResults": 6,

    "limit": 1000,

    "offset": 0,

    "count": 6,

    "hasMore": false

}

With the count of 2 for the message "Opportunities created." I know the Opportunities in the data upload were successfully imported. If you'd like to see the Opportunities in Eloqua, follow these instructions. To be sure, I searched in Eloqua for the Opportunities I uploaded:

 

Now that we have Opportunities created in Eloqua, let's create an Opportunity Contact linkage import definition to link them to Contacts:

 

Before creating the definition, here are some requirements and notes for the Opportunity Contact linkage import definition:

  • The {{Opportunity.Id}} field is required within fields
  • At least one Contact or Account field is required within fields
  • For linking by Account, the Account field set for Account Linkage must be used to match
  • There is a maximum of two fields allowed
  • identifierFieldName is a read only field set to the linkOpportunitiesMatchFieldName value, and should not specified in definition

 

     Request

POST /api/bulk/2.0/opportunities/contacts/imports

Content-Type: application/json

     Request - Body

{

  "name": "Opportunity Contact Linkage Import",

  "fields": {

    "EmailAddress": "{{Opportunity.Contact.Field(C_EmailAddress)}}",

    "OpportunityID": "{{Opportunity.Id}}"

  },

  "linkOpportunitiesCaseSensitiveMatchField": false,

  "linkOpportunitiesCaseSensitiveSourceField": false,

  "linkOpportunitiesEntityType": "Contact",

  "linkOpportunitiesMatchFieldName": "OpportunityID",

  "linkOpportunitiesMultipleSourceMatches": true,

  "linkOpportunitiesSourceField": "EmailAddress",

  "isSyncTriggeredOnImport": true

}

     Response

201 Created

{

    "linkOpportunitiesMatchFieldName": "OpportunityID",

    "linkOpportunitiesSourceField": "EmailAddress",

    "linkOpportunitiesEntityType": "Contact",

    "linkOpportunitiesCaseSensitiveSourceField": false,

    "linkOpportunitiesCaseSensitiveMatchField": false,

    "linkOpportunitiesMultipleSourceMatches": true,

    "name": "Opportunity Contact Linkage Import",

    "fields": {

        "EmailAddress": "{{Opportunity.Contact.Field(C_EmailAddress)}}",

        "OpportunityID": "{{Opportunity.Id}}"

    },

    "identifierFieldName": "OpportunityID",

    "isSyncTriggeredOnImport": true,

    "dataRetentionDuration": "P7D",

    "uri": "/opportunities/contacts/imports/18820",

    "createdBy": "API.User",

    "createdAt": "2018-04-30T19:15:39.6474055Z",

    "updatedBy": "API.User",

    "updatedAt": "2018-04-30T19:15:39.6474055Z"

}

In our import definition, we are stating that we want to link Contacts by their email address to specific Opportunities, by OpportunityID. Once documentation is published, I will provide a link here. (Documentation now published: API documentation and tutorial) In the mean time, here are descriptions for all the new properties:

 

PropertyTypeDescription
linkOpportunitiesCaseSensitiveMatchFieldbooleanWhether or not to perform a case sensitive search on the match field.
linkOpportunitiesCaseSensitiveSourceFieldbooleanWhether or not to perform a case sensitive search on the source field.
linkOpportunitiesEntityTypestringSpecifies the entity of the contact linkage import. Allowed values are "Contact" or "Account".
linkOpportunitiesMatchFieldNamestringSpecifies the field name for matching.
linkOpportunitiesMultipleSourceMatchesbooleanWhether or not imported data will be mapped to multiple matching records.
linkOpportunitiesSourceFieldstringSpecifies the source field name for matching.

 

Now that we have our definition created we can upload data to the definition to link Opportunities to Contacts:

 

Note: All Contacts were confirmed created prior to upload, except "notacontact@xyzcompany.com", to demonstrate what happens if the Contact does not exist.

 

     Request

POST /api/Bulk/2.0/opportunities/contacts/imports/18820/data

Content-Type: application/json

     Request - Body

[

  {

    "OpportunityID": "1ABC",

    "EmailAddress": "contact@abccompany.com"

  },

  {

    "OpportunityID": "1ABC",

    "EmailAddress": "contact2@abccompany.com"

  },

  {

    "OpportunityID": "2XYZ",

    "EmailAddress": "contact@xyzcompany.com"

  },

  {

    "OpportunityID": "2XYZ",

    "EmailAddress": "notacontact@xyzcompany.com"

  }

]

     Response

{

    "syncedInstanceUri": "/opportunities/contacts/imports/18820",

    "status": "pending",

    "createdAt": "2018-04-30T19:47:34.6272339Z",

    "createdBy": "API.User",

    "uri": "/syncs/27421"

}

As I knew I could upload all data in one request to the data endpoint, I set the isSyncTriggeredOnImport property to true, so the response to the data upload is the created sync. Now I'll retrieve the sync logs to confirm the Opportunities were linked:

 

     Request

GET /api/Bulk/2.0/syncs/27421/logs

     Response

{

    "items": [

        {

            "syncUri": "/syncs/27421",

            "count": 4,

            "severity": "information",

            "statusCode": "ELQ-00130",

            "message": "Total records staged for import.",

            "createdAt": "2018-04-30T19:48:03.0570000Z"

        },

        {

            "syncUri": "/syncs/27421",

            "count": 0,

            "severity": "information",

            "statusCode": "ELQ-00137",

            "message": "Ready for data import processing.",

            "createdAt": "2018-04-30T19:48:03.0800000Z"

        },

        {

            "syncUri": "/syncs/27421",

            "count": 0,

            "severity": "information",

            "statusCode": "ELQ-00101",

            "message": "Sync processed for sync , resulting in Warning status.",

            "createdAt": "2018-04-30T19:48:32.6700000Z"

        },

        {

            "syncUri": "/syncs/27421",

            "count": 4,

            "severity": "information",

            "statusCode": "ELQ-00001",

            "message": "Total records processed.",

            "createdAt": "2018-04-30T19:48:02.3930000Z"

        },

        {

            "syncUri": "/syncs/27421",

            "count": 1,

            "severity": "warning",

            "statusCode": "ELQ-00085",

            "message": "Contact does not exist.",

            "createdAt": "2018-04-30T19:48:07.9070000Z"

        },

        {

            "syncUri": "/syncs/27421",

            "count": 3,

            "severity": "information",

            "statusCode": "ELQ-00058",

            "message": "Opportunities mapped to contacts.",

            "createdAt": "2018-04-30T19:48:07.9070000Z"

        }

    ],

    "totalResults": 6,

    "limit": 1000,

    "offset": 0,

    "count": 6,

    "hasMore": false

}

As expected we see a count of 3 with the message of "Opportunities mapped to contacts.", and a count of 1 with the message "Contact does not exist." Now to see the details on which record referenced a Contact that did not exist I make a request to the rejects endpoint:

 

     Request

GET /api/Bulk/2.0/syncs/27421/rejects

     Response

{

    "items": [

        {

            "fieldValues": {

                "EmailAddress": "notacontact@xyzcompany.com",

                "OpportunityID": "2XYZ"

            },

            "message": "Contact does not exist.",

            "statusCode": "ELQ-00085",

            "recordIndex": 4,

            "invalidFields": []

        }

    ],

    "totalResults": 1,

    "limit": 1000,

    "offset": 0,

    "count": 1,

    "hasMore": false

}

As expected we see notacontact@xyzcompany.com appear with the message "Contact does not exist." If an Opportunity doesn't exist the message is "Opportunity does not exist." with a statusCode of "ELQ-00084".

 

We've retrieved Opportunity fields, used those fields to import Opportunities, then completed the scenario by linking the Opportunities to Contacts, and with that we've completed the walk-through of the Opportunities endpoints coming to the Bulk API.

 

When will I receive the 18B release? Visit the Oracle Eloqua Release Center to view roll out dates.

 

How about documentation? On May 18, 2018, the applicable Oracle Eloqua Developer Help Center reference and tutorial pages will be published. (Documentation now published: API documentation and tutorial)

 

Interested in more related to the 18B release?

With the Oracle Eloqua 491 release, Contact fields will be available on all Bulk API activity exports, and a Dream will be partially implemented with SMTP fields being added to Bulk API hard Bounceback exports.

 

Contact Fields on Bulk API Activity Exports

  • Up to 10 Contact fields will be available to add in an activity export definition
    • If more than 10 Contact fields are included, Eloqua will respond with a 400 validation error
    • There should be an expected addition to export time when adding Contact fields to an activity export
  • Any Contact field returned via the Retrieve a list of contact field definitions endpoint will be available
  • Including Contact fields on activity exports enables two common fields for matching activities to Contacts to be available for all activity types: Contact ID Ext and Email Address
  • Contact attributes (e.g. Id) and special fields (e.g. IsBounced, IsSbuscribed, Format) are not available with this addition
    • We are planning to add Contact Id to the remaining activity types it's not currently available (Subscribe, Unsubscribe, Bounceback) in the future
    • There are no plans to add special fields, if there are use cases for these fields, please share
  • The syntax for a Contact field statement in a Bulk API activity export definition will be:
{{Activity.Contact.Field(<contact_field_name>)}}
  • Example Email Open activity export definition with a Contact field:

POST /api/bulk/2.0/activities/exports

{

  "filter": "'{{Activity.Type}}'='EmailOpen'",

  "name": "Bulk Activity Export - Email Open",

  "fields": {

    "ActivityId": "{{Activity.Id}}",

    "ActivityType": "{{Activity.Type}}",

    "ActivityDate": "{{Activity.CreatedAt}}",

    "ContactId": "{{Activity.Contact.Id}}",

    "IpAddress": "{{Activity.Field(IpAddress)}}",

    "VisitorId": "{{Activity.Visitor.Id}}",

    "VisitorExternalId": "{{Activity.Visitor.ExternalId}}",

    "EmailRecipientId": "{{Activity.Field(EmailRecipientId)}}",

    "AssetType": "{{Activity.Asset.Type}}",

    "AssetName": "{{Activity.Asset.Name}}",

    "AssetId": "{{Activity.Asset.Id}}",

    "SubjectLine": "{{Activity.Field(SubjectLine)}}",

    "EmailWebLink": "{{Activity.Field(EmailWebLink)}}",

    "CampaignId": "{{Activity.Campaign.Id}}",

    "ExternalId": "{{Activity.ExternalId}}",

    "DeploymentId": "{{Activity.Field(EmailDeploymentId)}}",

    "EmailSendType": "{{Activity.Field(EmailSendType)}}",

    "EmailAddress": "{{Activity.Field(EmailAddress)}}",

    "ContactIdExt": "{{Activity.Contact.Field(ContactIDExt)}}"

  }

}

 

SMTP Fields on Bulk API Hard Bounceback Exports

 

Here is an example Bounceback export definition as a preview of the field statements:

POST /API/Bulk/2.0/activities/exports

{

  "filter": "'{{Activity.Type}}'='Bounceback'",

  "name": "Bulk Activity Export - Bounceback",

  "fields": {

    "ActivityId": "{{Activity.Id}}",

    "ActivityType": "{{Activity.Type}}",

    "ActivityDate": "{{Activity.CreatedAt}}",

    "EmailAddress": "{{Activity.Field(EmailAddress)}}",

    "AssetType": "{{Activity.Asset.Type}}",

    "AssetName": "{{Activity.Asset.Name}}",

    "AssetId": "{{Activity.Asset.Id}}",

    "CampaignId": "{{Activity.Campaign.Id}}",

    "ExternalId": "{{Activity.ExternalId}}",

    "EmailRecipientId":"{{Activity.Field(EmailRecipientId)}}",

    "DeploymentId": "{{Activity.Field(EmailDeploymentId)}}",

    "SmtpErrorCode": "{{Activity.Field(SmtpErrorCode)}}",

    "SmtpStatusCode": "{{Activity.Field(SmtpStatusCode)}}",

    "SmtpMessage": "{{Activity.Field(SmtpMessage)}}"

  }

}

 

SMTP Activity field details:

Field typeData typeMax lengthDescription
{{Activity.Field(SmtpErrorCode)}}String9The SMTP Status Code for the email bounceback.
{{Activity.Field(SmtpStatusCode)}}String3The SMTP Response Code for the email bounceback.
{{Activity.Field(SmtpMessage)}}String510The SMTP message for the email bounceback.

And one more addition! In the above Bounceback example export definition, there are two additional fields available: EmailRecipientId and EmailDeploymentId. These fields enable tying a hard Bounceback to an email send.

 

When will I receive the 491 release? Visit the Oracle Eloqua Release Center to view roll out dates.

 

How about documentation? When version 491 is rolled out to POD2, the applicable Oracle Eloqua Developer Help Center reference and tutorial pages will be updated.

 

Interested in more related to the 491 release? When version 491 is rolled out to POD2:

I was recently asked if you could build an Eloqua Campaign including the steps using the REST API. The Developer Help Center includes details and an example on creating an Eloqua Campaign using this REST 2.0 API endpoint - POST /assets/campaign; however, there are not details for creating the steps, which can be done using the elements array, within the Request parameters tab or within the Example. We are in the process of updating our documentation to indicate all the Request parameters and include a more extensive Example.

 

While our official documentation is being updated, I wanted to provide an example to demonstrate creating an Eloqua Campaign including the steps using the REST 2.0 API endpoint - POST /assets/campaign. Here is a screenshot of the resulting Eloqua Campaign created using the REST API Request below:

create_campaign_example.png

 

Request

POST /API/REST/2.0/assets/campaign

 

Request - Body

{

  "name": "API Example Campaign",

  "elements": [

    {

      "type": "CampaignSegment",

      "id": "-1",

      "name": "Segment members",

      "segmentId": "2",

      "outputTerminals": [

        {

          "type": "CampaignOutputTerminal",

          "connectedId": "-2",

          "connectedType": "CampaignWaitAction",

          "terminalType": "out"

        }

      ],

      "position": {

        "type": "Position",

        "x": "365",

        "y": "39"

      },

      "isFinished": "false",

      "isRecurring": "false"

    },

    {

      "type": "CampaignWaitAction",

      "id": "-2",

      "name": "Wait",

      "outputTerminals": [

        {

          "type": "CampaignOutputTerminal",

          "connectedId": "-3",

          "connectedType": "CampaignEmail",

          "terminalType": "out"

        }

      ],

      "position": {

        "type": "Position",

        "x": "365",

        "y": "131"

      },

      "waitFor": "3600"

    },

    {

      "type": "CampaignEmail",

      "emailId": "98",

      "id": "-3",

      "name": "Email 1",

      "outputTerminals": [

        {

          "type": "CampaignOutputTerminal",

          "connectedId": "-4",

          "connectedType": "CampaignWaitAction",

          "terminalType": "out"

        }

      ],

      "position": {

        "type": "Position",

        "x": "365",

        "y": "222"

      },

      "includeListUnsubscribeHeader": "false",

      "isAllowingResend": "false",

      "isAllowingSentToMasterExclude": "false",

      "isAllowingSentToUnsubscribe": "false",

      "sendTimePeriod": "sendAllEmailAtOnce"

    },

    {

      "type": "CampaignWaitAction",

      "id": "-4",

      "name": "Wait",

      "outputTerminals": [

        {

          "type": "CampaignOutputTerminal",

          "connectedId": "-5",

          "connectedType": "CampaignEmail",

          "terminalType": "out"

        }

      ],

      "position": {

        "type": "Position",

        "x": "365",

        "y": "316"

      },

      "waitFor": "259200"

    },

    {

      "type": "CampaignEmail",

      "emailId": "99",

      "id": "-5",

      "name": "Email 2",

      "outputTerminals": [

        {

          "type": "CampaignOutputTerminal",

          "connectedId": "-6",

          "connectedType": "CampaignMoveToContactListAction",

          "terminalType": "out"

        }

      ],

      "position": {

        "type": "Position",

        "x": "365",

        "y": "409"

      },

      "includeListUnsubscribeHeader": "true",

      "isAllowingResend": "false",

      "isAllowingSentToMasterExclude": "false",

      "isAllowingSentToUnsubscribe": "false",

      "sendTimePeriod": "sendAllEmailAtOnce"

    },

    {

      "type": "CampaignEmailClickthroughRule",

      "emailId": "99",

      "id": "-6",

      "name": "Clicked Email?",

      "outputTerminals": [

        {

          "type": "CampaignOutputTerminal",

          "id": "-7",

          "connectedId": "-9",

          "connectedType": "CampaignMoveToContactListAction",

          "terminalType": "no"

        },

        {

          "type": "CampaignOutputTerminal",

          "id": "-8",

          "connectedId": "-10",

          "connectedType": "CampaignMoveToContactListAction",

          "terminalType": "yes"

        }

      ],

      "position": {

        "type": "Position",

        "x": "365",

        "y": "497"

      },

      "evaluateNoAfter": "604800",

      "numberOfClicks": "1",

      "withinLast": "604800"

    },

    {

      "type": "CampaignMoveToContactListAction",

      "listId": "64",

      "id": "-9",

      "name": "Move to Shared List 1",

      "position": {

        "type": "Position",

        "x": "233",

        "y": "614"

      }

    },

    {

      "type": "CampaignMoveToContactListAction",

      "listId": "65",

      "id": "-10",

      "name": "Move to Shared List 2",

      "position": {

        "type": "Position",

        "x": "503",

        "y": "614"

      }

    }

  ]

}

 

Response

{

  "type": "Campaign",

  "currentStatus": "Draft",

  "id": "22",

  "createdAt": "1463284937",

  "createdBy": "11",

  "depth": "complete",

  "folderId": "308",

  "name": "API Example Campaign",

  "permissions": [

    "Retrieve",

    "SetSecurity",

    "Delete",

    "Update",

    "Activate"

  ],

  "updatedAt": "1463284937",

  "updatedBy": "11",

  "elements": [

    {

      "type": "CampaignSegment",

      "id": "337",

      "initialId": "-1",

      "name": "Segment members",

      "memberCount": "0",

      "outputTerminals": [

        {

          "type": "CampaignOutputTerminal",

          "id": "320",

          "connectedId": "338",

          "connectedType": "CampaignWaitAction",

          "terminalType": "out"

        }

      ],

      "position": {

        "type": "Position",

        "x": "365",

        "y": "39"

      },

      "isFinished": "false",

      "isRecurring": "false",

      "segmentId": "2"

    },

    {

      "type": "CampaignWaitAction",

      "id": "338",

      "initialId": "-2",

      "name": "Wait",

      "memberCount": "0",

      "outputTerminals": [

        {

          "type": "CampaignOutputTerminal",

          "id": "321",

          "connectedId": "339",

          "connectedType": "CampaignEmail",

          "terminalType": "out"

        }

      ],

      "position": {

        "type": "Position",

        "x": "365",

        "y": "131"

      },

      "waitFor": "3600"

    },

    {

      "type": "CampaignEmail",

      "id": "339",

      "initialId": "-3",

      "name": "Email 1",

      "memberCount": "0",

      "outputTerminals": [

        {

          "type": "CampaignOutputTerminal",

          "id": "322",

          "connectedId": "340",

          "connectedType": "CampaignWaitAction",

          "terminalType": "out"

        }

      ],

      "position": {

        "type": "Position",

        "x": "365",

        "y": "222"

      },

      "emailId": "98",

      "includeListUnsubscribeHeader": "false",

      "isAllowingResend": "false",

      "isAllowingSentToMasterExclude": "false",

      "isAllowingSentToUnsubscribe": "false",

      "sendTimePeriod": "sendAllEmailAtOnce"

    },

    {

      "type": "CampaignWaitAction",

      "id": "340",

      "initialId": "-4",

      "name": "Wait",

      "memberCount": "0",

      "outputTerminals": [

        {

          "type": "CampaignOutputTerminal",

          "id": "323",

          "connectedId": "341",

          "connectedType": "CampaignEmail",

          "terminalType": "out"

        }

      ],

      "position": {

        "type": "Position",

        "x": "365",

        "y": "316"

      },

      "waitFor": "259200"

    },

    {

      "type": "CampaignEmail",

      "id": "341",

      "initialId": "-5",

      "name": "Email 2",

      "memberCount": "0",

      "outputTerminals": [

        {

          "type": "CampaignOutputTerminal",

          "id": "324",

          "connectedId": "342",

          "connectedType": "CampaignEmailClickthroughRule",

          "terminalType": "out"

        }

      ],

      "position": {

        "type": "Position",

        "x": "365",

        "y": "409"

      },

      "emailId": "99",

      "includeListUnsubscribeHeader": "true",

      "isAllowingResend": "false",

      "isAllowingSentToMasterExclude": "false",

      "isAllowingSentToUnsubscribe": "false",

      "sendTimePeriod": "sendAllEmailAtOnce"

    },

    {

      "type": "CampaignEmailClickthroughRule",

      "id": "342",

      "initialId": "-6",

      "name": "Clicked Email?",

      "memberCount": "0",

      "outputTerminals": [

        {

          "type": "CampaignOutputTerminal",

          "id": "325",

          "initialId": "-7",

          "connectedId": "343",

          "connectedType": "CampaignMoveToContactListAction",

          "terminalType": "no"

        },

        {

          "type": "CampaignOutputTerminal",

          "id": "326",

          "initialId": "-8",

          "connectedId": "344",

          "connectedType": "CampaignMoveToContactListAction",

          "terminalType": "yes"

        }

      ],

      "position": {

        "type": "Position",

        "x": "365",

        "y": "497"

      },

      "emailId": "99",

      "evaluateNoAfter": "604800",

      "numberOfClicks": "1",

      "withinLast": "604800"

    },

    {

      "type": "CampaignMoveToContactListAction",

      "id": "343",

      "initialId": "-9",

      "name": "Move to Shared List 1",

      "memberCount": "0",

      "position": {

        "type": "Position",

        "x": "233",

        "y": "614"

      },

      "listId": "64"

    },

    {

      "type": "CampaignMoveToContactListAction",

      "id": "344",

      "initialId": "-10",

      "name": "Move to Shared List 2",

      "memberCount": "0",

      "position": {

        "type": "Position",

        "x": "503",

        "y": "614"

      },

      "listId": "65"

    }

  ],

  "isReadOnly": "false",

  "actualCost": "0",

  "budgetedCost": "0",

  "campaignCategory": "contact",

  "campaignType": "",

  "fieldValues": [

    {

      "type": "FieldValue",

      "id": "4",

      "value": ""

    },

    {

      "type": "FieldValue",

      "id": "5",

      "value": ""

    },

    {

      "type": "FieldValue",

      "id": "6",

      "value": ""

    },

    {

      "type": "FieldValue",

      "id": "7",

      "value": ""

    }

  ],

  "isEmailMarketingCampaign": "false",

  "isIncludedInROI": "false",

  "isMemberAllowedReEntry": "false",

  "isSyncedWithCRM": "false",

  "product": "",

  "region": ""

}

 

If you've made it this far, I bet you are wondering about the negative numbers used in the Request - Body. These are reference ids and they must be unique negative numbers. Eloqua will set these ids when the Campaign is created; therefore, you cannot know what they are prior. The negative id is a placeholder that allows you to connect the steps within the output terminals. For the output terminals, the unique negative number reference id is only needed for the output terminal itself if there is more than one output terminal, i.e. a yes / no decision step.

 

This is a simple example, but should help as a starting point for creating more complex flows. To see how you'd build more complex flows using a REST API call, and how other steps look within the elements array, retrieve an existing Campaign you already created using this REST 2.0 API endpoint - GET /assets/campaign/{id}?depth=complete (do not forget the "?depth=complete"!).

 

Happy coding with the Eloqua APIs and see you around Code It!

Before diving into the flowchart for exporting all Eloqua activities using the the Bulk API, let's cover some best practices using the Bulk API focused on exporting activities:

  • Use filters to ensure fewer than 5,000,000 records are exported per Bulk API sync
    • A common method of filtering is to use the Activity Date to export a finite time period of activity, such as one month (use larger or smaller time periods based on volume of activity generation)
  • Export one Activity Type at a time
    • Do not run syncs for all Activity Types simultaneously
  • Make multiple export requests sequentially
    • Allow the sync to finish before executing the next sync

 

Here are the main resources related to exporting activities using the Bulk API on the Developer Help Center:

 

Exporting All Eloqua Activities Using the Bulk API Flowchart

(click to enlarge)

Exporting All Activities Using the Bulk API Flow

 

Notes and Resources by Flowchart Step

Determine Bulk API Base URL and Select Authentication

Select Activity Type to Export

Create an Activity Export Definition for the selected Activity Type

Create the Sync with the Activity Export Definition URI

Retrieve Sync Logs with the Sync URI's Logs Endpoint

    • Syncs Logs Endpoint
    • If exporting to CSV:
      • The total number of records returned can only be retrieved using the Syncs Logs Endpoint
    • If exporting to JSON:
      • When retrieving data with the Sync URI's Data Endpoint in JSON, the following parameters are returned along with the data, either of which could be used to determine how many calls are necessary to retrieve the remaining data:
        • totalResults
        • hasMore
    • When retrieving the count of total records exported from the Syncs Logs endpoint, it's important to note the "message" within that item will be "Successfully exported members to csv file."
    • Example Syncs Logs Response with items "count" and "message" highlighted:

              Example Sync Logs Response

Retrieve the Data with the Sync URI's Data Endpoint

Retrieve the Remaining Data with the Sync URI's Data Endpoint Using Offset

    • Example with URL parameters: GET /syncs/{id}/data?limit=50000&offset=50000
    • Example calls to retrieve all data if there were exactly 150,000 activity records:
      • GET /syncs/{id}/data?limit=50000&offset=0 (This is part of "Retrieve the Data with the Sync URI's Data Endpoint" step)
      • GET /syncs/{id}/data?limit=50000&offset=50000
      • GET /syncs/{id}/data?limit=50000&offset=100000
    • Data returned in JSON unless Accept header specifics CSV (API Call Format)
      • Example: Accept: text/csv
    • A validation check could also be added here.

Create an Activity Export Definition Filtering on Activity Created Date

With the Oracle Eloqua 478/479 release, lead scoring models and fields are coming to the Bulk API. I am going to walk through an end to end scenario with examples, but before I do that here are some of the highlights:

  • Lead scoring models and their corresponding lead scoring fields will be discoverable via the Bulk API
  • Lead scoring fields will be available to be used in Bulk API contact export definitions
    • There could only be one lead scoring model used per contact export definition
  • Each lead scoring model has three lead scoring fields available:
    • Rating
    • Profile Score
    • Engagement Score
  • Lead scoring fields can be used in contact export filter statements

 

With the key takeaways highlighted, let's now take a look at this new functionality in action. I'm going to walk through finding a lead scoring model, using those lead scoring fields in a contact export definition, and completing the scenario by retrieving some contacts. Along the way we'll also use a lead scoring field in a filter statement and, as a bonus, I'll be highlighting an additional improvement in regards to contact exports. Total FOMO should now be in place!

 

Let's start by retrieving two lead scoring models:

 

Request

GET /API/Bulk/2.0/contacts/scoring/models?limit=2

Response

200 OK

 

{

  "items": [

    {

      "name": "Test 1",

      "status": "Draft",

      "id": 1,

      "fields": [

        {

          "name": "Rating",

          "statement": "{{Contact.LeadScore.Model[1].Rating}}",

          "dataType": "string"

        },

        {

          "name": "ProfileScore",

          "statement": "{{Contact.LeadScore.Model[1].ProfileScore}}",

          "dataType": "number"

        },

        {

          "name": "EngagementScore",

          "statement": "{{Contact.LeadScore.Model[1].EngagementScore}}",

          "dataType": "number"

        }

      ],

      "uri": "/contacts/scoring/models/1",

      "createdBy": "Lou.Patrick",

      "updatedBy": "Lou.Patrick",

      "createdAt": "2015-01-15T18:44:58.9930000Z",

      "updatedAt": "2015-07-02T18:03:19.1930000Z"

    },

    {

      "name": "Test 2",

      "status": "Draft",

      "id": 2,

      "fields": [

        {

          "name": "Rating",

          "statement": "{{Contact.LeadScore.Model[2].Rating}}",

          "dataType": "string"

        },

        {

          "name": "ProfileScore",

          "statement": "{{Contact.LeadScore.Model[2].ProfileScore}}",

          "dataType": "number"

        },

        {

          "name": "EngagementScore",

          "statement": "{{Contact.LeadScore.Model[2].EngagementScore}}",

          "dataType": "number"

        }

      ],

      "uri": "/contacts/scoring/models/2",

      "createdBy": "Lou.Patrick",

      "updatedBy": "Lou.Patrick",

      "createdAt": "2015-01-15T18:45:09.9930000Z",

      "updatedAt": "2015-07-02T18:18:01.6570000Z"

    }

  ],

  "totalResults": 6,

  "limit": 2,

  "offset": 0,

  "count": 2,

  "hasMore": true

}

 

I do not see the lead scoring model I'm looking for; however, I see there are four more lead scoring models. I could remove the limit and return all lead scoring models but I know the name of the lead scoring model I want so let's use a query parameter to retrieve a lead scoring model by name and, because I'm not sure on the exact name, we'll use some wildcards:

 

Request

GET /API/Bulk/2.0/contacts/scoring/models?q="name=*Oscar*"

Response

200 OK

 

{

  "items": [

    {

      "name": "Oscar Cleaning",

      "status": "Active",

      "id": 6,

      "fields": [

        {

          "name": "Rating",

          "statement": "{{Contact.LeadScore.Model[6].Rating}}",

          "dataType": "string"

        },

        {

          "name": "ProfileScore",

          "statement": "{{Contact.LeadScore.Model[6].ProfileScore}}",

          "dataType": "number"

        },

        {

          "name": "EngagementScore",

          "statement": "{{Contact.LeadScore.Model[6].EngagementScore}}",

          "dataType": "number"

        }

      ],

      "uri": "/contacts/scoring/models/6",

      "createdBy": "Lou.Patrick",

      "updatedBy": "Lou.Patrick",

      "createdAt": "2016-03-22T17:20:54.6100000Z",

      "updatedAt": "2016-03-22T18:20:39.3000000Z"

    }

  ],

  "totalResults": 1,

  "limit": 1000,

  "offset": 0,

  "count": 1,

  "hasMore": false

}

 

There's the one I'm looking for. Now let's create a contact export definition using the lead scoring fields for the Oscar Cleaning lead scoring model:

250_fields.png

Request

POST /API/Bulk/2.0/contacts/exports

Request - Body

{

  "name": "Contact Lead Score Export - Oscar Cleaning",

  "fields": {

    "EmailAddress": "{{Contact.Field(C_EmailAddress)}}",

    "FirstName": "{{Contact.Field(C_FirstName)}}",

    "LastName": "{{Contact.Field(C_LastName)}}",

    "Rating": "{{Contact.LeadScore.Model[6].Rating}}",

    "Profile": "{{Contact.LeadScore.Model[6].ProfileScore}}",

    "Engagement": "{{Contact.LeadScore.Model[6].EngagementScore}}",

    "AcademyAwards": "{{Contact.Field(C_Academy_Awards1)}}"

  }

}

Response

201 Created

 

{

  "name": "Contact Lead Score Export - Oscar Cleaning",

  "fields": {

    "EmailAddress": "{{Contact.Field(C_EmailAddress)}}",

    "FirstName": "{{Contact.Field(C_FirstName)}}",

    "LastName": "{{Contact.Field(C_LastName)}}",

    "Rating": "{{Contact.LeadScore.Model[6].Rating}}",

    "Profile": "{{Contact.LeadScore.Model[6].ProfileScore}}",

    "Engagement": "{{Contact.LeadScore.Model[6].EngagementScore}}",

    "AcademyAwards": "{{Contact.Field(C_Academy_Awards1)}}"

  },

  "dataRetentionDuration": "PT12H",

  "uri": "/contacts/exports/5",

  "createdBy": "Lou.Patrick",

  "createdAt": "2016-03-22T19:22:05.2099774Z",

  "updatedBy": "Lou.Patrick",

  "updatedAt": "2016-03-22T19:22:05.2099774Z"

}

 

With our uri in hand, we are now ready to create the sync:

 

Request

POST /API/Bulk/2.0/syncs

Request - Body

{

  "syncedInstanceUri": "/contacts/exports/5"

}

Response

201 Created

 

{

  "syncedInstanceUri": "/contacts/exports/5",

  "status": "pending",

  "createdAt": "2016-03-22T19:34:44.7980550Z",

  "createdBy": "Lou.Patrick",

  "uri": "/syncs/2"

}

 

Ideally we'd be using the callbackUrl parameter to allow Eloqua to alert us when the sync is complete. For simplicity of demonstration, I'll GET the sync details to confirm it was successful:

 

Request

GET /API/Bulk/2.0/syncs/2

Response

200 OK

 

{

  "syncedInstanceUri": "/contacts/exports/5",

  "syncStartedAt": "2016-03-22T19:34:47.2570000Z",

  "syncEndedAt": "2016-03-22T19:34:48.5000000Z",

  "status": "success",

  "createdAt": "2016-03-22T19:34:45.0030000Z",

  "createdBy": "Lou.Patrick",

  "uri": "/syncs/2"

}

 

Success! Now it's time to retrieve the data:

 

Request

GET /API/Bulk/2.0/syncs/2/data

Response

200 OK

 

{

  "totalResults": 13,

  "limit": 1000,

  "offset": 0,

  "count": 13,

  "hasMore": false,

  "items": [

    {

      "EmailAddress": "test1234@test.com",

      "FirstName": "",

      "LastName": "",

      "Rating": "D4",

      "Profile": "0",

      "Engagement": "0",

      "AcademyAwards": ""

    },

    {

      "EmailAddress": "lou.patrick@oracle.com",

      "FirstName": "Lou",

      "LastName": "Patrick",

      "Rating": "D4",

      "Profile": "0",

      "Engagement": "0",

      "AcademyAwards": ""

    },

    {

      "EmailAddress": "test12@test.com",

      "FirstName": "Test12",

      "LastName": "",

      "Rating": "D4",

      "Profile": "0",

      "Engagement": "0",

      "AcademyAwards": ""

    },

    {

      "EmailAddress": "test1234@test1234.com",

      "FirstName": "",

      "LastName": "",

      "Rating": "D4",

      "Profile": "0",

      "Engagement": "0",

      "AcademyAwards": ""

    },

    {

      "EmailAddress": "12321321312test@test.com",

      "FirstName": "",

      "LastName": "",

      "Rating": "D4",

      "Profile": "0",

      "Engagement": "0",

      "AcademyAwards": ""

    },

    {

      "EmailAddress": "123456@test.com",

      "FirstName": "",

      "LastName": "",

      "Rating": "D4",

      "Profile": "0",

      "Engagement": "0",

      "AcademyAwards": ""

    },

    {

      "EmailAddress": "whoopi@egot.com",

      "FirstName": "Whoopi",

      "LastName": "Goldberg",

      "Rating": "B1",

      "Profile": "70",

      "Engagement": "100",

      "AcademyAwards": "1.0000"

    },

    {

      "EmailAddress": "streep@gmail.com",

      "FirstName": "Meryl",

      "LastName": "Streep",

      "Rating": "A3",

      "Profile": "100",

      "Engagement": "30",

      "AcademyAwards": "3.0000"

    },

    {

      "EmailAddress": "thanks@yahoo.com",

      "FirstName": "Tom",

      "LastName": "Hanks",

      "Rating": "A4",

      "Profile": "80",

      "Engagement": "0",

      "AcademyAwards": "2.0000"

    },

    {

      "EmailAddress": "cruise@gmail.com",

      "FirstName": "Tom",

      "LastName": "Cruise",

      "Rating": "D1",

      "Profile": "0",

      "Engagement": "100",

      "AcademyAwards": ""

    },

    {

      "EmailAddress": "arnold@schawrz.com",

      "FirstName": "Arnold",

      "LastName": "Schwarzenegger",

      "Rating": "D2",

      "Profile": "0",

      "Engagement": "70",

      "AcademyAwards": ""

    },

    {

      "EmailAddress": "jack@gmail.com",

      "FirstName": "Jack",

      "LastName": "Nicholson",

      "Rating": "A1",

      "Profile": "100",

      "Engagement": "100",

      "AcademyAwards": "3.0000"

    },

    {

      "EmailAddress": "nats@yahoo.com",

      "FirstName": "Natalie",

      "LastName": "Portman",

      "Rating": "B4",

      "Profile": "70",

      "Engagement": "0",

      "AcademyAwards": "1.0000"

    }

  ]

}

 

There are not many contacts in this test instance but there are some clear test contacts and also some contacts we'd want to remove as they do not have any Academy Awards. Next, we'll create a new contact export definition and filter on the Profile lead scoring field, removing all contacts from our export with a Profile value of "0":

 

Request

POST /API/Bulk/2.0/contacts/exports

Request - Body

{

  "name": "Contact Lead Score Export - Oscar Cleaning - Filtered",

  "fields": {

    "EmailAddress": "{{Contact.Field(C_EmailAddress)}}",

    "FirstName": "{{Contact.Field(C_FirstName)}}",

    "LastName": "{{Contact.Field(C_LastName)}}",

    "Rating": "{{Contact.LeadScore.Model[6].Rating}}",

    "Profile": "{{Contact.LeadScore.Model[6].ProfileScore}}",

    "Engagement": "{{Contact.LeadScore.Model[6].EngagementScore}}",

    "AcademyAwards": "{{Contact.Field(C_Academy_Awards1)}}"

  },

  "filter": "'{{Contact.LeadScore.Model[6].ProfileScore}}' > '0'"

}

Response

201 Created

 

{

  "name": "Contact Lead Score Export - Oscar Cleaning - Filtered",

  "fields": {

    "EmailAddress": "{{Contact.Field(C_EmailAddress)}}",

    "FirstName": "{{Contact.Field(C_FirstName)}}",

    "LastName": "{{Contact.Field(C_LastName)}}",

    "Rating": "{{Contact.LeadScore.Model[6].Rating}}",

    "Profile": "{{Contact.LeadScore.Model[6].ProfileScore}}",

    "Engagement": "{{Contact.LeadScore.Model[6].EngagementScore}}",

    "AcademyAwards": "{{Contact.Field(C_Academy_Awards1)}}"

  },

  "filter": "'{{Contact.LeadScore.Model[6].ProfileScore}}' > '0'",

  "dataRetentionDuration": "PT12H",

  "uri": "/contacts/exports/6",

  "createdBy": "Lou.Patrick",

  "createdAt": "2016-03-22T20:16:32.6099108Z",

  "updatedBy": "Lou.Patrick",

  "updatedAt": "2016-03-22T20:16:32.6099108Z"

}

 

We'll skip over showing examples for creating the sync and confirming it was successful, as it's identical to the examples above. From creating the sync the resulting uri is "/syncs/3". Let's jump to retrieving data from our filtered contact export definition sync:

 

Request

GET /API/Bulk/2.0/syncs/3/data

Response

200 OK

 

{

  "totalResults": 5,

  "limit": 1000,

  "offset": 0,

  "count": 5,

  "hasMore": false,

  "items": [

    {

      "EmailAddress": "whoopi@egot.com",

      "FirstName": "Whoopi",

      "LastName": "Goldberg",

      "Rating": "B1",

      "Profile": "70",

      "Engagement": "100",

      "AcademyAwards": "1.0000"

    },

    {

      "EmailAddress": "streep@gmail.com",

      "FirstName": "Meryl",

      "LastName": "Streep",

      "Rating": "A3",

      "Profile": "100",

      "Engagement": "30",

      "AcademyAwards": "3.0000"

    },

    {

      "EmailAddress": "thanks@yahoo.com",

      "FirstName": "Tom",

      "LastName": "Hanks",

      "Rating": "A4",

      "Profile": "80",

      "Engagement": "0",

      "AcademyAwards": "2.0000"

    },

    {

      "EmailAddress": "jack@gmail.com",

      "FirstName": "Jack",

      "LastName": "Nicholson",

      "Rating": "A1",

      "Profile": "100",

      "Engagement": "100",

      "AcademyAwards": "3.0000"

    },

    {

      "EmailAddress": "nats@yahoo.com",

      "FirstName": "Natalie",

      "LastName": "Portman",

      "Rating": "B4",

      "Profile": "70",

      "Engagement": "0",

      "AcademyAwards": "1.0000"

    }

  ]

}

 

Now we have filtered results including only contacts that have at least one Academy Award. That completes the walk through of the lead scoring capabilities coming to the Bulk API. So now I bet everyone wants to mark their calendars with the date 478/479 will arrive for them!

 

When will I receive the 478/479 release? Visit the Oracle Eloqua Release Center to view roll out dates.

 

How about documentation? When version 478/479 is rolled out to POD2, the Oracle Eloqua Developer Help Center will be updated with the new endpoints and examples. (Documentation now updated: Lead scoring models)

 

Interested in more related to Eloqua APIs and the 478/479 release? Take a look at the Developer Release Notes - 478/479.

 

Happy coding with the Eloqua APIs and see you around Code It!