Create and Associate Leads, Companies, and Opportunities with the Marketo REST API

August 7, 2015 | by

In order to take full advantage of Marketo analytics it is crucial to build out correct and robust associations between your lead, company and opportunity records.  When you are not leveraging a native CRM-sync, building these relationships can pose some difficulties, so today we’ll walk through building them.

Object Relationships

In Marketo, there are a few vital relationships to fully establish opportunity reporting:

  • Leads and Opportunities have a many to many relationship via the OpportunityRole object.
  • OpportunityRole has both a leadId and externalopportunityid field to create the relationship from lead to opportunity.
  • In order to qualify for a Has Opportunity smart list filter, a lead must have an OpportunityRole related to an opportunity.
  • Opportunities have a many-to-one relationship to the Company object via the externalCompanyId field.
  • Leads have a one-to-many relationship to Companies via the externalCompanyId field.
  • Opportunities are attributed to a program based on a lead’s Acquisition Program or their membership and success in a program (See Understanding Attribution).

Building out these relationships across your lead database will allow you to fully leverage Marketo analytics and see the influence that your programs have on opportunity creation and win rates.


The simplest way to build out these relationships is by starting with company creation.  This ensures that we can pass externalCompanyId to our opportunities during creation, instead of having to perform additional API calls to update opportunities after they’ve been created.  Depending on existing configuration, this may or may not be a necessary step, but net new leads and contacts with associated companies will need to have these records added to your Marketo instance in order for the relationships to be built, so let’s take a look at some code to create company records.

The companies API provides two deduplication options, set by the dedupeBy parameter in the request, “dedupeFields” and “idField.”  These can be explicitly retrieved by calling Describe Companies.  If dedupeBy is unset, it defaults to dedupeFields.  In the case of company records, dedupeFields always corresponds to “externalCompanyId,” which is an arbitrary string set by an external source, and idField, corresponding to the “marketoId” field, which is an integer generated and returned by Marketo after creation.  Depending on the selection for dedupeBy, one of externalCompanyId or marketoId must be included in any upsert call for a company record.  These same requirements apply to the Opportunity and Opportunity Role object APIs.

Our code exposes two constructors: one accepting a single argument of an Auth object, and another which accepts Auth and a list of JsonObject company records.  If constructed without an input List, then company records must be added through the addCompanies method, which will check create a new ArrayList if the input is null, and then add all JsonObject arguments to the input List. Here’s an example usage:

We’re creating a single company JsonObject with just one field, externalCompanyId, then constructing an instance of UpsertCompanies, and adding our company to the input list with addCompanies.


Similar to the company objects, the opportunity API has a dedupeBy parameter, accepting “dedupeFields” or “idField,” corresponding to “externalopportunityid” and “marketoGUID” respectively.  So here’s our code, which looks quite similar to the UpsertCompanies class:

The same constructor options are provided, taking an Auth or Auth+List<JsonObject>, and an addOpportunities method to input JsonObject opportunities.  Here’s a usage example:

Here, we’re creating two example opportunities and then giving them values for the name, externalopportunityid, externalCompanyId, and externalCreatedDate fields.  We haven’t discussed externalCreatedDate yet, but it is important to utilize since it is treated as the master field in RCE for when an opportunity was created, making it important for correct attribution.  You can use your organization’s business logic to determine what you input in this field, based on whether you’re backfilling existing opportunity data, or creating new ones on the fly.  We’ll create our instance of UpsertOpportunities and then add our JsonObjects via addOpportunities.  Now that the instance is configured you can push this to Marketo with postData and print out your result


Roles are quite similar to the preceding two objects, except that they have a slightly different requirement when setting dedupeBy to dedupeFields.  Roles require that three fields be included when creating or updating a record via this method, “leadId,” “role,” and”externalopportunityid.”  “role” may be any string value, but the other two must refer to a valid Id of a lead, and a valid Id of an opportunity respectively.

We’re following a similar pattern for the constructors and addRoles method as the previous examples.  Here’s an example.

Here we’re creating the new JsonObjects for our 2 example roles, and adding their required dedupeFields, pulling the externalopportunityid from the opportunities we already created, then pushing them down to Marketo.

Putting It All Together

Here is the complete example of our main method:

You can see the sequence of creating companies, opportunities, and roles.  Now you’re all set to sync your Company and Opportunity data to Marketo.