How to Import Contacts w/ Multipart Endpoint

Moderator

How to Import Contacts w/ Multipart Endpoint

One of the most efficient ways to add or update contacts using the v2 API is to use the Import Contacts multipart endpoint. It requires that you have a file in one of the supported formats with the contact information to be imported.

Why is using multipart bulk activity your best bet for adding and updating contacts?

 

  • You can easily package the contact data by exporting from your database to a file.
  • The POST creates an asynchronous job that runs in the background
  • No worries about hitting API call limits - you can add/update up to 20,000 contacts with a single API call
  • Only individual contacts with format problems fail, not the entire job
  • monitor status using the location value in the response header

What’s needed

There are 3 major elements to making this happen:

  1. The contact data to import.
  2. The list(s) you want to add the contacts to.
  3. The multipart post code that calls the endpoint and passes the file.

How to format the import data

 

File types
The following file types are supported by the Import Contacts bulk activity endpoint:

 

  • .txt
  • .csv
  • .xls
  • .xlsx 

File format

The import file consists of columns, one for each contact property included, and rows, one for each contact being imported.

 

EMAIL

FIRST NAME

LAST NAME

HOME PHONE

CITY

US STATE/CA PROVINCE

COUNTRY

CUSTOM FIELD 1

contact@example.com

John

Doe

1115555555

Des Moines

IA

US

red

contact2@example.com

Rapunzel

Longhair

5555551111

Port Charlotte

FL

US

green

contact3@example.com

Joaquin

Arroyo

2222222222

Montreal

QC

CA

blue

 

Contact Properties

 

You can include the following contact properties in your import file. You must name the column containing the property as shown below:

  • EMAIL (also E-MAIL, EMAIL ADDRESS, E-MAIL ADDRESS)
  • FIRST NAME
  • MIDDLE NAME
  • LAST NAME
  • JOB TITLE
  • COMPANY NAME
  • WORK PHONE
  • HOME PHONE
  • ADDRESS LINE 1 (to 3)
  •  CITY
  • STATE
  • US STATE/CA PROVINCE
  • COUNTRY
  • ZIP/POSTAL CODE
  • SUB ZIP/POSTAL CODE
  • CUSTOM FIELD 1 (to 15)

Limitations

 

There are two limitations that you need to be aware of:

 

  1. File size: the import file must be less than 4 megabytes.
  2. Number of contacts: Each POST can import a maximum 20,000 contacts.

The activity request will fail if the payload is greater than 4 MBs or if there are more 20,000 contacts.

 

Examples

 

Here are a few code examples using the Import Contacts multipart endpoint to import contacts from a file to a contact list.

 

CURL Example

 

Here’s an example CURL script that uploads the file contacts.txt to the import contacts multipart endpoint, adding the contacts to listId=5. It assumes that the data file is located in the same directory from which the script is run, otherwise you must use an absolute path to the data file.

 

curl -i -H "Authorization: Bearer <Oauth2.0_access_token>" -H "Content-Type: multipart/form-data" -H "Accept: application/json" -X POST -F 'lists=5' -F 'file_name=contacts.txt'  -F 'data=@contacts.txt' "https://api.constantcontact.com/v2/activities/addcontacts?api_key=<api_key>"

 

Java Example

 

Here's an example of a multipart post written in Java that imports the file contacts.txt, adding the contacts to list 1 (listId=1).

 

final HttpClient httpclient = new DefaultHttpClient();
   final HttpPost httppost = new HttpPost("https://api.constantcontact.com/v2/activities/addcontacts?api_key=<api_key>");

   httppost.addHeader("Authorization", "Bearer <OAuth2.0_access_token>");
   httppost.addHeader("Accept", ”application/json”);
   httppost.addHeader("Content-Type", "multipart/form-data");

   final File fileToUse = new File("/path_to_file/contacts.txt");  //e.g. /temp/contact.txt
   final FileBody data = new FileBody(fileToUse);
   final String listIds = "1";
   final StringBody lists = new StringBody(listIds);
   final StringBody fileName = new StringBody(fileToUse.getName());
   final MultipartEntity reqEntity = new MultipartEntity();
   reqEntity.addPart("file_name", fileName);
   reqEntity.addPart("lists", lists);
   reqEntity.addPart("data", data);

   httppost.setEntity(reqEntity);

   final HttpResponse response = httpclient.execute(httppost);
   final HttpEntity resEntity = response.getEntity();

   EntityUtils.consume(resEntity);

  httpclient.getConnectionManager().shutdown();
}

 

Java SDK

 

The Constant Contact Java SDK includes methods for all bulk activities, including the import contacts multipart bulk activity endpoint.  Here’s a snippet of code that uses the ctct.addBulkContactsMultipart Java SDK method to import the contacts in file.xlsx and add them to contact lists 4 and 5.

 

System.out.println("Multipart test...");

 

   File f = new File("file.xlsx");

 

   ArrayList<String> listIds = new ArrayList<String>();

   listIds.add("4"); listIds.add("5");

 

   try { ContactsResponse response1 =

   ctct.addBulkContactsMultipart("file.xlsx", f, listIds);

   System.out.println(response1.toJSON()); } catch

   (ConstantContactServiceException e) { List<CUrlRequestError> errors =

   e.getErrorInfo(); for (CUrlRequestError error : errors) {

   System.out.println(error.getErrorMessage()); } }

 

Ruby Example

 

This example coded in Ruby is available with all supporting files in our Ruby SDK.

 

#

# myapp.rb

# ConstantContact

#

# Copyright (c) 2013 Constant Contact. All rights reserved.

 

require 'rubygems'

require 'sinatra'

require 'active_support'

require 'yaml'

require 'constantcontact'

 

# This is a Sinatra application (http://www.sinatrarb.com/).

# Update config.yml with your data before running the application.

# Run this application like this : ruby myapp.rb

 

# Name this action according to your

# Constant Contact API Redirect URL, see config.yml

get '/cc_callback' do

    cnf = YAML::load(File.open('config/config.yml'))

 

    @oauth = ConstantContact::Auth::OAuth2.new(

      :api_key => cnf['api_key'],

      :api_secret => cnf['api_secret'],

      :redirect_url => cnf['redirect_url']

    )

 

    @error = params[:error]

    @user = params[:username]

    @code = params[:code]

 

    if @code

      begin

        @lists = []

 

        response = @oauth.get_access_token(@code)

        @token = response['access_token']

 

        cc = ConstantContact::Api.new(cnf['api_key'])

        lists = cc.get_lists(@token)

        if lists

          lists.each do |list|

            # Select the first list, by default

            selected = list == lists.first

            @lists << {

              'id' => list.id,

              'name' => list.name,

              'selected' => selected

            }

          end

        end

 

      rescue => e

        message = parse_exception(e)

        @error = "An error occured when saving the contacts : " + message

      end

      erb :contacts_multipart

    else

      erb :callback

    end

end

 

# Name this action according to your

# Constant Contact API Redirect URL, see config.yml

post '/cc_callback' do

    cnf = YAML::load(File.open('config/config.yml'))

 

    @error = params[:error]

    @user = params[:username]

    @code = params[:code]

    @token = params[:token]

 

    if @code

      cc = ConstantContact::Api.new(cnf['api_key'])

 

      @activity = params[:activity]

      lists = params[:lists] || {}

      lists['checkboxes'] = [] if lists['checkboxes'].blank?

 

      @lists = []

      if lists['ids']

        lists['ids'].each do |key, list_id|

          list_name = lists['names'][key]

          selected = !(lists['checkboxes'].blank? || lists['checkboxes'][key].blank?)

          @lists << {

            'id' => list_id,

            'name' => list_name,

            'selected' => selected

          }

        end

      end

 

      begin

        if @activity

          # Validate

          raise 'Please select a file' if @activity['file'].blank?

          file_name = @activity['file'][:filename]

          contents = @activity['file'][:tempfile].read

 

          add_to_lists = []

          lists['ids'].each do |key, list_id|

            add_to_lists << list_id if lists['checkboxes'][key]

          end

          add_to_lists = add_to_lists.join(',')

 

          if /remove_contacts/.match(file_name)

            cc.add_remove_contacts_from_lists_activity_from_file(@token, file_name, contents, add_to_lists)

          elsif /add_contacts/.match(file_name)

            cc.add_create_contacts_activity_from_file(@token, file_name, contents, add_to_lists)

          end

 

          redirect '/cc_callback'

        end

      rescue => e

        message = parse_exception(e)

        @error = "An error occured when saving the contacts : " + message

        #puts e.backtrace

      end

      erb :contacts_multipart

    else

      erb :callback

    end

end

 

def parse_exception(e)

  if e.respond_to?(:response)

    hash_error = JSON.parse(e.response)

    message = hash_error.first['error_message']

  else

    message = e.message

  end

  message.to_s

end

Rich Marcucella
Sr. Technical Writer, Content Developer
Web Services Team
http://developer.constantcontact.com
5 REPLIES 5

Hi Rich,

 

Thanks for providing details for multipart imports.

 

One question: the Java sample code provided does not work for me. Could you please elaborate on what parts of the Java code I must modify myself? I've personalized the obvious ones like API key, authorization token, etc., but I feel that I could be missing something.

 

The code I used is:

 

{
final HttpClient httpclient = new DefaultHttpClient();
final HttpPost httppost = new HttpPost("https://api.constantcontact.com/v2/activities/addcontacts?api_key=<I put my API key>");

httppost.addHeader("Authorization", "Bearer <I put my access token here>");
httppost.addHeader("Accept", "application/json”);
httppost.addHeader("content-type", "multipart/form-data");

final File fileToUse = new File("C:\Users\c16196\Documents\Work\Projects\APIConstantContact\Test File POST Call.xlsx");
final FileBody data = new FileBody(fileToUse);
final String listIds = "1";
final StringBody lists = new StringBody(listIds);
final StringBody fileName = new StringBody(fileToUse.getName());
final MultipartEntity reqEntity = new MultipartEntity();
reqEntity.addPart("file_name", fileName);
reqEntity.addPart("lists", lists);
reqEntity.addPart("data", data);

httppost.setEntity(reqEntity);

final HttpResponse response = httpclient.execute(httppost);
final HttpEntity resEntity = response.getEntity();

EntityUtils.consume(resEntity);

httpclient.getConnectionManager().shutdown();
}

 

The response I received is:

 

[{
   
"error_key": "http.header.content_type.invalid",
   
"error_message": "Invalid content type. API only supports application/json."
}]

 

Any help you can offer helps me.

 

Thank you,

Nick W

Hi Nick,

 

Try changing 

httppost.addHeader("content-type", "multipart/form-data");

to

httppost.addHeader("Content-Type", "multipart/form-data");

 

I apologize if the sample is incorrect, and I will update it if this indeed fixes the issue.

 

In the meantime, we will test the sample code again, and see if we run into any issues as you have.

 

Regards,

Rich Marcucella
Sr. Technical Writer, Content Developer
Web Services Team
http://developer.constantcontact.com

Richard,

 

Thank you for the reply, and sorry for my late response. 

 

Capitalizing the 'C' didn't work, but I just realized that this may be a simple firewall issue on my side (I have yet to whitelist api.constantcontact.com). I'll whitelist the domain, try it again, and reply back if it still doesn't work.

 

Thanks,

Nick

I got that same response.. I am wonder what really goes into the data field.  Since we are uploading a file, just questioning what goes there.  Seems I am close.

 

<cfhttp url="https://api.constantcontact.com/v2/activities/addcontacts?api_key=" method="post" result="cc_results"
file="C:\Intranet\filelocation\load.xlsx"
username="username"
password="password">
<cfhttpparam type="header" name="content-type" value="multipart/form-data">
<cfhttpparam type="header" name="accept" value="application/json">
<cfhttpparam type="header" name="Authorization" value="Bearer code">
<cfhttpparam type="formField" name="lists" value="listid">
<cfhttpparam type="formField" name="data" value="First Name,Last Name,EMAIL"> ?????
<cfhttpparam type="formField" name="file_name" value="load.xlsx"> ?????
<cfhttpparam type="formField" name="activityType" value="ADD_CONTACTS">
</cfhttp>

 

now I get:

Filecontentorg.apache.cxf.interceptor.Fault: Couldn't determine the boundary from the message!
HeaderHTTP/1.1 500 Server Error Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate, no-cache="Set-Cookie" Content-Type: application/json Date: Wed, 19 Nov 2014 18:22:25 GMT Pragma: no-cache Server: Apache Vary: Accept-Encoding,User-Agent X-Mashery-Responder: prod-j-worker-us-east-1b-55.mashery.com X-Powered-By: Content-Length: 83 Connection: Close

Hello,

 

Based on what you are sending and the error response, there are a couple of things happening:

 

  1. The error response is responding that there is an issue with the boundaries used to separate data in the multipart request. I am unfortunately not an expert in ColdFusion, but it appears that it is attempting to simply attach information as HTTP URL parameters, which is not the correct way to perform a multipart POST.
  2. You are sending some fields incorrectly for the import.

For the first part, there is an important distinction that with a multipart POST you are construction a POST body that uses boundaries to separate the individual items in the POST, rather than appending them to the URL as this code appears to be doing. This is very important because the multipart request needs to include binary data. Unfortunately I am not particularly skilled with ColdFusion and am not aware of what the best method for a proper multipart request in ColdFusion is.

 

For the second part, the information that should be sent is as follows:

data - Binary contents of the file being uploaded

file_name - Name of the file being uploaded

lists - Comma separated set of list IDs

activityType - This does NOT need to be sent. In the V2 API, the URL determines the activity type.

 

Hopefully this information is enough to get things moving again. If you continue to have trouble, please let us know!

 

Best Regards,

Elijah G.
API Support Engineer
Developer Portal

View API documentation, code samples, get your API key.

Visit Page