Google Apps Profiles API (Deprecated)

Notice
The Google Apps Profiles API has been officially deprecated as of May 15, 2013. It has been replaced by the Admin SDK’s Directory API. The Google Apps Profiles API will continue to work as per our Deprecation Policy.

Google Apps Profiles Data API Developer's Guide

The Profiles Data API allows client applications to retrieve and update profile information for users in a Google Apps domain. Each user profile is stored in the owning user's Google Account, but it can be modified only by an administrator of the user's domain.

Your client application can use the Profiles Data API to retrieve the profiles of users in a Google Apps domain and to modify them on behalf of an administrator.

Profiles cannot be inserted or deleted, since every user is considered to have a profile, even if it is blank. Your client can populate a profile using an update operation. It can send an update operation with no content to clear existing profiles.

This document also provides examples of how to manipulate profiles using XML and HTTP. After reading this document, you may wish to learn more about interacting with the API using our client libraries by reading the programming-language-specific sections of this developer's guide.

Note: This API is only available to Google Apps for Business and Google Apps for Education domains.

Contents

Audience

This document is intended for programmers who want to write client applications that can interact with Google Apps users' profiles using raw XML/HTTP requests, Java, .NET and/or Python.

This document assumes that you understand the general ideas behind the Google Data APIs protocol and the Google Contacts API.

Getting Started

To test a program that uses the Google Apps Profiles API, you will need the username and password of a Google Apps administrator account. For testing purposes, you may want to register a test domain.

Running the sample code

Protocol


If you're using a UNIX system and you want to try the examples in this document without writing any code, you may find the UNIX command-line utilities curl or wget useful; for more information, see the manual pages for those utilities.

Java

A full working sample client, containing all the sample code shown in this document, is available in the Java client library distribution, atgdata/java/sample/contacts/ContactsExample.java. Build and execution instructions are included in the README.txt file.

The sample client performs several operations on contacts to demonstrate the use of the Contacts Data API.

To compile the examples in this document into your own code, you'll need the following import statements:

import com.google.gdata.client.*;
import com.google.gdata.client.contacts.*;
import com.google.gdata.data.*;
import com.google.gdata.data.contacts.*;
import com.google.gdata.data.extensions.*;
import com.google.gdata.util.*;
import java.io.IOException;
import java.net.URL;

...
    ContactsService myService = new ContactsService("YOUR_APPLICATION_NAME");
    // Authorize the service object.
...

.NET

To compile the examples in this document into your own code, you'll need to download the latest .NET client library distribution and use the following using statements:

using Google.Contacts;
using Google.GData.Contacts;
using Google.GData.Client;
using Google.GData.Extensions;

...
    RequestSettings settings = new RequestSettings("YOUR_APPLICATION_NAME");
    // Add authorization token.
    ...
    ContactsRequest cr = new ContactsRequest(settings);
...

Python

A full working sample client, containing all the sample code shown in this document, is available in the Python client library distribution, at samples/contacts/contacts_example.py.

The sample client performs several operations on contacts to demonstrate the use of the Contacts Data API.

To run the examples in this document in your own code, you'll need the following import statements:

import atom.data
import gdata.data
import gdata.contacts.client
import gdata.contacts.data

...
  gd_client = gdata.contacts.client.ContactsClient(source='YOUR_APPLICATION_NAME',
                                                 domain='domainName')
  # Authorize the client.
...

Specifying a version

Every request that you send using the Contacts Data API should specify version 3.0 of the API.

To specify a version number, use the GData-Version HTTP header:

GData-Version: 3.0

Alternatively, if you can't set HTTP headers, you can specify v=3.0 as a query parameter in the URL. The HTTP header is preferred where possible.

Note: The client libraries supply appropriate version headers automatically, so don't use the v=3.0 query parameter when you're using a client library.

Retrieving profiles

Retrieving all profiles

To retrieve all user profiles for a domain, send an authorized GET request to the following URL:

https://www.google.com/m8/feeds/profiles/domain/domainName/full

With the appropriate value in place of domainName.

Upon success, the server responds with a HTTP 200 OK status code and the requested contacts feed.

Protocol

Request:

GET /m8/feeds/profiles/domain/domainName/full

Response:

HTTP/1.1 200 OK
Content-Type: application/atom+xml; charset=UTF-8; type=feed
...

<feed xmlns='http://www.w3.org/2005/Atom'
    xmlns:openSearch='http://a9.com/-/spec/opensearch/1.1/'
    xmlns:gContact='http://schemas.google.com/contact/2008'
    xmlns:batch='http://schemas.google.com/gdata/batch'
    xmlns:gd='http://schemas.google.com/g/2005'
    gd:etag='feedEtag'>
  <id>userEmail</id>
  <updated>2008-12-10T10:04:15.446Z</updated>
  <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#contact'/>
  <title>domainName's Profiles</title>
  <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml'
    href='https://www.google.com/m8/feeds/contacts/userEmail/full'/>
  <link rel='http://schemas.google.com/g/2005#post' type='application/atom+xml'
    href='https://www.google.com/m8/feeds/contacts/userEmail/full'/>
  <link rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml'
    href='https://www.google.com/m8/feeds/contacts/userEmail/full/batch'/>
  <link rel='self' type='application/atom+xml'
    href='https://www.google.com/m8/feeds/contacts/userEmail/full?max-results=25'/>
  <author>
    <name>domainName</name>
  </author>
  <generator version='1.0' uri='http://www.google.com/m8/feeds'>Contacts</generator>
  <openSearch:totalResults>1</openSearch:totalResults>
  <openSearch:startIndex>1</openSearch:startIndex>
  <openSearch:itemsPerPage>25</openSearch:itemsPerPage>
  <entry gd:etag='contactEtag'>
    <category scheme='http://schemas.google.com/g/2005#kind'
      term='http://schemas.google.com/contact/2008#profile' />
    <id>http://www.google.com/m8/feeds/profiles/domain/domainName/full/userName</id>
    <link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*'
      href='https://www.google.com/m8/feeds/photos/profile/domainName/userName'
      gd:etag='span class="apiparam">photoEtag' />
    <link rel='self' type='application/atom+xml'
       href='https://www.google.com/m8/feeds/profiles/domain/domainName/userName' />
    <link rel='edit' type='application/atom+xml'
       href='https://www.google.com/m8/feeds/profiles/domain/domainName/userName' />
    <title>Elizabeth Bennet</title>
    <gd:name>
      <gd:givenName>Elizabeth</gd:givenName>
      <gd:familyName>Bennet</gd:familyName>
    </gd:name>
    <gd:structuredPostalAddress rel='http://schemas.google.com/g/2005#work'>
      <gd:formattedAddress>Longbourne, Nr. Meryton, Hertfordshire, England</gd:formattedAddress>
    </gd:structuredPostalAddress>
    <gContact:status indexed='true' / >
  </entry>
  ...
</feed>
  

Note: The feed may not contain all of the user profiles, because there's a default limit on the number of results returned. For more information, see the max-results query parameter in Retrieving profiles using query parameters.

Java

public static void printAllProfiles(ContactsService myService)
    throws ServiceException, IOException {
  // Request the feed
  URL feedUrl = new URL("https://www.google.com/m8/feeds/profiles/domain/domainName/full");
  ContactFeed resultFeed = myService.getFeed(feedUrl, ContactFeed.class);
  // Print the results
  System.out.println(resultFeed.getTitle().getPlainText());
  for (ContactEntry entry : resultFeed.getEntries()) {
    if (entry.hasName()) {
      Name name = entry.getName();
      if (name.hasFullName()) {
        String fullNameToDisplay = name.getFullName().getValue();
        if (name.getFullName().hasYomi()) {
          fullNameToDisplay += " (" + name.getFullName().getYomi() + ")";
        }
        System.out.println("\t\t" + fullNameToDisplay);
      } else {
        System.out.println("\t\t (no full name found)");
      }
      if (name.hasNamePrefix()) {
        System.out.println("\t\t" + name.getNamePrefix().getValue());
      } else {
        System.out.println("\t\t (no name prefix found)");
      }
      if (name.hasGivenName()) {
        String givenNameToDisplay = name.getGivenName().getValue();
        if (name.getGivenName().hasYomi()) {
          givenNameToDisplay += " (" + name.getGivenName().getYomi() + ")";
        }
        System.out.println("\t\t" + givenNameToDisplay);
      } else {
        System.out.println("\t\t (no given name found)");
      }
      if (name.hasAdditionalName()) {
        String additionalNameToDisplay = name.getAdditionalName().getValue();
        if (name.getAdditionalName().hasYomi()) {
          additionalNameToDisplay += " (" + name.getAdditionalName().getYomi() + ")";
        }
        System.out.println("\t\t" + additionalNameToDisplay);
      } else {
        System.out.println("\t\t (no additional name found)");
      }
      if (name.hasFamilyName()) {
        String familyNameToDisplay = name.getFamilyName().getValue();
        if (name.getFamilyName().hasYomi()) {
          familyNameToDisplay += " (" + name.getFamilyName().getYomi() + ")";
        }
        System.out.println("\t\t" + familyNameToDisplay);
      } else {
        System.out.println("\t\t (no family name found)");
      }
      if (name.hasNameSuffix()) {
        System.out.println("\t\t" + name.getNameSuffix().getValue());
      } else {
        System.out.println("\t\t (no name suffix found)");
      }
    } else {
      System.out.println("\t (no name found)");
    }

    System.out.println("Email addresses:");
    for (Email email : entry.getEmailAddresses()) {
      System.out.print(" " + email.getAddress());
      if (email.getRel() != null) {
        System.out.print(" rel:" + email.getRel());
      }
      if (email.getLabel() != null) {
        System.out.print(" label:" + email.getLabel());
      }
      if (email.getPrimary()) {
        System.out.print(" (primary) ");
      }
      System.out.print("\n");
    }

    System.out.println("IM addresses:");
    for (Im im : entry.getImAddresses()) {
      System.out.print(" " + im.getAddress());
      if (im.getLabel() != null) {
        System.out.print(" label:" + im.getLabel());
      }
      if (im.getRel() != null) {
        System.out.print(" rel:" + im.getRel());
      }
      if (im.getProtocol() != null) {
        System.out.print(" protocol:" + im.getProtocol());
      }
      if (im.getPrimary()) {
        System.out.print(" (primary) ");
      }
      System.out.print("\n");
    }

    System.out.println("Extended Properties:");
    for (ExtendedProperty property : entry.getExtendedProperties()) {
      if (property.getValue() != null) {
        System.out.println("  " + property.getName() + "(value) = " +
            property.getValue());
      } else if (property.getXmlBlob() != null) {
        System.out.println("  " + property.getName() + "(xmlBlob)= " +
            property.getXmlBlob().getBlob());
      }
    }

    Link photoLink = entry.getContactPhotoLink();
    String photoLinkHref = photoLink.getHref();
    System.out.println("Photo Link: " + photoLinkHref);

    if (photoLink.getEtag() != null) {
      System.out.println("Profile Photo's ETag: " + photoLink.getEtag());
    }

    System.out.println("Profile's ETag: " + entry.getEtag());
  }
}

Note: The feed may not contain all of the user profiles, because there's a default limit on the number of results returned. For more information, see the setMaxResults method in Retrieving profiles using query parameters.

.NET

void PrintAllProfiles(ContactsRequest cr)
{
  ContactsQuery query = new ContactsQuery("https://www.google.com/m8/feeds/profiles/domain/domainName/full");
  Feed<Contact> f = cr.Get<Contact>(query);
  foreach (Contact entry in f.Entries)
  {
    if (entry.Name != null)
    {
      Name name = entry.Name;
      if (!string.IsNullOrEmpty(name.FullName))
        Console.WriteLine("\t\t" + name.FullName);
      else
        Console.WriteLine("\t\t (no full name found)");
      if (!string.IsNullOrEmpty(name.NamePrefix))
        Console.WriteLine("\t\t" + name.NamePrefix);
      else
        Console.WriteLine("\t\t (no name prefix found)");
      if (!string.IsNullOrEmpty(name.GivenName))
      {
        string givenNameToDisplay = name.GivenName;
        if (!string.IsNullOrEmpty(name.GivenNamePhonetics))
          givenNameToDisplay += " (" + name.GivenNamePhonetics + ")";
        Console.WriteLine("\t\t" + givenNameToDisplay);
      }
      else
        Console.WriteLine("\t\t (no given name found)");
      if (!string.IsNullOrEmpty(name.AdditionalName))
      {
        string additionalNameToDisplay = name.AdditionalName;
        if (string.IsNullOrEmpty(name.AdditionalNamePhonetics))
          additionalNameToDisplay += " (" + name.AdditionalNamePhonetics + ")";
        Console.WriteLine("\t\t" + additionalNameToDisplay);
      }
      else
        Console.WriteLine("\t\t (no additional name found)");
      if (!string.IsNullOrEmpty(name.FamilyName))
      {
        string familyNameToDisplay = name.FamilyName;
        if (!string.IsNullOrEmpty(name.FamilyNamePhonetics))
          familyNameToDisplay += " (" + name.FamilyNamePhonetics + ")";
        Console.WriteLine("\t\t" + familyNameToDisplay);
      }
      else
        Console.WriteLine("\t\t (no family name found)");
      if (!string.IsNullOrEmpty(name.NameSuffix))
        Console.WriteLine("\t\t" + name.NameSuffix);
      else
        Console.WriteLine("\t\t (no name suffix found)");
    }
    else
      Console.WriteLine("\t (no name found)");

    foreach (EMail email in entry.Emails)
    {
      Console.WriteLine("\t" + email.Address);
    }
  }
}

Note: The feed may not contain all of the user's profiles, because there's a default limit on the number of results returned. For more information, see the NumberToRetrieve query argument in Retrieving profiles using query parameters.

Python

def PrintAllProfiles(gd_client):
  feed = gd_client.GetProfilesFeed()

  for i, entry in enumerate(feed.entry):
    print '\n%s %s' % (i+1, entry.name.full_name.text)
    if entry.content:
      print '    %s' % (entry.content.text)
    # Display the primary email address for the contact.
    for email in entry.email:
      if email.primary and email.primary == 'true':
        print '    %s' % (email.address)
    # Display extended properties.
    for extended_property in entry.extended_property:
      if extended_property.value:
        value = extended_property.value
      else:
        value = extended_property.GetXmlBlob()
      print '    Extended Property - %s: %s' % (extended_property.name, value)

Note: The feed may not contain all of the user's profiles, because there's a default limit on the number of results returned. For more information, see the max_results query argument in Retrieving profiles using query parameters.

Retrieving profiles using query parameters

The Profiles Data API lets you request a set of profiles that match specified criteria.

List of supported query parameters:

Protocol

start-key
An opaque key that encodes the position (in the full feed) of the first entry to return. You cannot construct a start-key value, but it is present in the next link of a previous Profiles Data API feed response. This allows pagination but not random access (in contrast to the start-index parameter used in typical GData queries).
max-results
The maximum number of entries to return. The server may respond with fewer entries. The default value is 25.

Example:

https://www.google.com/m8/feeds/profiles/domain/domainName/full?max-results=maxResult&start-key=startKey

With the appropriate values in place of domainName, maxResult and startKey.

Java

setStringCustomParameter("start-key", startKey)
An opaque key that encodes the position (in the full feed) of the first entry to return. You cannot construct a start-key value, but it is present in the next link of a previous Profiles Data API feed response. This allows pagination but not random access (in contrast to the start-index parameter used in typical GData queries).
setMaxResults
The maximum number of entries to return. The server may respond with fewer entries. The default value is 25.
public void queryProfiles(ContactsService myService, int maxResults, string startKey) throws ServiceException,
    IOException {
  // Create query and submit a request
  URL feedUrl = new URL("https://www.google.com/m8/feeds/domain/domainName/full");
  Query myQuery = new Query(feedUrl);
  myQuery.setMaxResults(maxResults);
  query.setStringCustomParameter("start-key", startKey);

  ContactFeed resultFeed = myService.query(myQuery, ContactFeed.class);

  for (ContactEntry profile : resultFeed.getEntries()) {
    // Do something with the profile entry.
  }
}

.NET

ExtraParameter = "start-key=" + startKey
An opaque key that encodes the position (in the full feed) of the first entry to return. You cannot construct a start-key value, but it is present in the next link of a previous Profiles Data API feed response. This allows pagination but not random access (in contrast to the start-index parameter used in typical GData queries).
NumberToRetrieve
The maximum number of entries to return. The server may respond with fewer entries. The default value is 25.
public void QueryProfiles(ContactsRequest cr, int maxResults, string startKey)
{
  ContactsQuery query = new ContactsQuery("https://www.google.com/m8/feeds/domain/domainName/full");
  query.NumberToRetrieve = maxResults;
  query.ExtraParameter = "start-key=" + startKey;

  Feed<Contact> feed = cr.Get<Contact>(query);
  foreach (Contact profile in feed.Entries)
  {
    // Do something with the profile entry.
  }
}

Python

start_key
An opaque key that encodes the position (in the full feed) of the first entry to return. You cannot construct a start-key value, but it is present in the next link of a previous Profiles Data API feed response. This allows pagination but not random access (in contrast to the start-index parameter used in typical GData queries).
max_results
The maximum number of entries to return. The server may respond with fewer entries. The default value is 25.
def query_profiles(gd_client, max_results, start_key):
  query = gdata.contacts.client.ProfilesQuery(max_results=max_results,
                                              start_key=start_key)
  feed = gd_client.GetProfiles(q = query)
  for profile in feed.entry:
    # Do something with the profile entry.
    pass

In particular, there is no support for full-text queries or ordering.

For more information about query parameters, see the Profiles Data API Reference Guide and the Google Data APIs Reference Guide.

Retrieving a single profile

To retrieve a single profile, send an authorized GET request to the profile's selfLink URL:

https://www.google.com/m8/feeds/profiles/domain/domainName/full/userName

With the appropriate values in place of domainEmail and userName.

Upon success, the server responds with an HTTP 200 OK status code and the requested contact entry.

Protocol

Request:

GET /m8/feeds/profiles/domain/domainName/full/userName

Response:

HTTP/1.1 200 OK
Content-Type: application/atom+xml; charset=UTF-8; type=feed
...

<entry xmlns='http://www.w3.org/2005/Atom'
    xmlns:gContact='http://schemas.google.com/contact/2008'
    xmlns:gd='http://schemas.google.com/g/2005'
    gd:etag='contactEtag'>
  <category scheme='http://schemas.google.com/g/2005#kind'
    term='http://schemas.google.com/contact/2008#profile' />
  <link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*'
    href='https://www.google.com/m8/feeds/photos/profile/domainName/userName'
    gd:etag='span class="apiparam">photoEtag' />
  <link rel='self' type='application/atom+xml'
     href='https://www.google.com/m8/feeds/profiles/domain/domainName/userName' />
  <link rel='edit' type='application/atom+xml'
     href='https://www.google.com/m8/feeds/profiles/domain/domainName/userName' />
  <title>Elizabeth Bennet</title>
  <gd:name>
    <gd:givenName>Elizabeth</gd:givenName>
    <gd:familyName>Bennet</gd:familyName>
  </gd:name>
  <gd:structuredPostalAddress rel='http://schemas.google.com/g/2005#work'>
    <gd:formattedAddress>Longbourne, Nr. Meryton, Hertfordshire, England</gd:formattedAddress>
  </gd:structuredPostalAddress>
  <gContact:status indexed='true' / >
</entry>

Java

public static ContactEntry retrieveProfile(ContactsService myService) {
  ContactEntry profile = myService.getEntry(
      new URL("https://www.google.com/m8/feeds/profiles/domain/domainName/full/userName"),
      ContactEntry.class);

  // Do something with the profile.
  return profile;
}

.NET

public static Contact retrieveProfile(ContactsRequest cr)
{
  Contact profile = cr.Retrieve<Contact>("https://www.google.com/m8/feeds/profiles/domain/domainName/full/userName");

  // Do something with the profile.
  return profile;
}

Python

def retrieve_contact(gd_client):
  profile = gd_client.GetProfile('https://www.google.com/m8/feeds/profiles/domain/domainName/full/userName')

  # Do something with the profile.
  return profile

Updating a profile

To update a profile, first retrieve the profile entry, modify the data and send an authorized PUT request to the profile's edit URL with the modified profile entry in the body.

The URL is of the form:

https://www.google.com/m8/feeds/profiles/domain/domainName/full/userName

With the appropriate values in place of domainName and userName.

To ensure that the data sent to the API doesn't overwrite another client's changes, the profile entry's Etag should be provided in the request header.

If-Match: Etag

If the Etag is outdated, the server responds with an HTTP 412 Precondition Failed status code.

Note: the special Etag value * can be used to bypass this verification and process the update regardless of updates from other clients.

For more information about ETags, see the Google Data APIs reference guide.

Upon success, the server responds with an HTTP 200 OK status code and the updated profile entry.

Protocol

Request:

PUT /m8/feeds/profiles/domain/domainName/full/userName
If-Match: Etag
...

<entry gd:etag='Etag'>
  <category scheme='http://schemas.google.com/g/2005#kind'
    term='http://schemas.google.com/contact/2008#profile' />
  <id>http://www.google.com/m8/feeds/photos/profile/domainName/userName</
  <updated>2008-02-28T18:47:02.303Z</updated>
  <link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*'
    href='https://www.google.com/m8/feeds/photos/profile/domainName/userName'
    gd:etag='span class="apiparam">photoEtag' />
  <link rel='self' type='application/atom+xml'
     href='https://www.google.com/m8/feeds/profiles/domainName/userName' />
  <link rel='edit' type='application/atom+xml'
     href='https://www.google.com/m8/feeds/profiles/domainName/userName' />
  <title>Elizabeth Bennet</title>
  <gd:name>
    <gd:givenName>Elizabeth</gd:givenName>
    <gd:familyName>Bennet</gd:familyName>
  </gd:name>
  <gd:structuredPostalAddress rel='http://schemas.google.com/g/2005#work'>
    <gd:formattedAddress>1600 Amphitheatre Pkwy, Mountain View, CA, 94043, United States</gd:formattedAddress>
  </gd:structuredPostalAddress>
  <gContact:status indexed='false' / >
</entry>

Response:

HTTP/1.1 200 OK
Content-Type: application/atom+xml; charset=UTF-8; type=entry
...

<entry gd:etag='newEtag'>
  <category scheme='http://schemas.google.com/g/2005#kind'
    term='http://schemas.google.com/contact/2008#profile' />
  <id>http://www.google.com/m8/feeds/photos/profile/domainName/userName</
  <updated>2011-05-11T09:30:00.000Z</updated>
  <link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*'
    href='https://www.google.com/m8/feeds/photos/profile/domainName/userName'
    gd:etag='span class="apiparam">photoEtag' />
  <link rel='self' type='application/atom+xml'
     href='https://www.google.com/m8/feeds/profiles/domainName/userName' />
  <link rel='edit' type='application/atom+xml'
     href='https://www.google.com/m8/feeds/profiles/domainName/userName' />
  <title>Elizabeth Bennet</title>
  <gd:name>
    <gd:givenName>Elizabeth</gd:givenName>
    <gd:familyName>Bennet</gd:familyName>
  </gd:name>
  <gd:structuredPostalAddress rel='http://schemas.google.com/g/2005#work'>
    <gd:formattedAddress>1600 Amphitheatre Pkwy, Mountain View, CA, 94043, United States</gd:formattedAddress>
  </gd:structuredPostalAddress>
  <gContact:status indexed='false' / >
</entry>

Java

public static ContactEntry updateContactName(
    ContactsService myService, URL contactURL)
    throws ServiceException, IOException {
  // First retrieve the contact to updated.
  ContactEntry entryToUpdate = myService.getEntry(contactURL, ContactEntry.class);

  if (entryToUpdate.getStructuredPostalAddresses().size() == 0) {
    entryToUpdate.addStructuredPostalAddress(new StructuredPostalAddress());
  }
  StructuredPostalAddress address = entryToUpdate.getStructuredPostalAddresses().get(0);

  address.setRel(Namespaces.inflate("work"));
  address.setFormattedAddress(new FormattedAddress(
          "1600 Amphitheatre Pkwy, Mountain View, CA, 94043, United States"));

  entryToUpdate.getStatus().setIndexed(false);

  URL editUrl = new URL(entryToUpdate.getEditLink().getHref());
  try {
    ContactEntry contactEntry = myService.update(editUrl, entryToUpdate);
    System.out.println("Updated: " + contactEntry.getUpdated().toString());
    return contactEntry;
  } catch (PreconditionFailedException e) {
    // Etags mismatch: handle the exception.
  }
  return null;
}

.NET

public static Contact UpdateContactName(ContactsRequest cr, Uri contactURL)
{
  // First, retrieve the contact to update.
  Contact contact = cr.Retrieve<Contact>(contactURL);

  if (contact.PostalAddresses.Count == 0)
  {
    contact.PostalAddresses.Add(
        new StructuredPostalAddress()
            {
              Rel: ContactsRelationships.IsWork
            });
  }

  contact.PostalAddresses[0].FormattedAddress = "1600 Amphitheatre Pkwy, Mountain View, CA, 94043, United States";
  contact.ContactEntryStatus.Indexed = False;

  try
  {
    Contact updatedContact = cr.Update(contact);
    Console.WriteLine("Updated: " + updatedEntry.Updated.ToString())
    return updatedContact;
  }
  catch (GDataVersionConflictException e)
  {
    // Etags mismatch: handle the exception.
  }
  return null;
}

Python

def update_contact_name(gd_client, profile_url):
  # First retrieve the contact to modify from the API.
  profile_entry = gd_client.GetProfile(profile_url)

  if len(profile_entry.structured_postal_address) == 0:
    profile_entry.structured_postal_address.append(
        gdata.data.StructuredPostalAddress(
            rel=gdata.data.WORK_REL))
  profile_entry.structured_postal_address[0].formatted_address = gdata.data.FormattedAddress(
      text='1600 Amphitheatre Pkwy, Mountain View, CA, 94043, United States')
  profile_entry.status.indexed = 'false'
  try:
    updated_profile = gd_client.Update(profile_entry)
    print 'Updated: %s' % updated_profile.updated.text
    return updated_profile
  except gdata.client.RequestError, e:
    if e.status == 412:
      # Etags mismatch: handle the exception.
      pass
  return None

Important: To ensure forward compatibility, be sure that when you PUT an updated entry you preserve all the XML that was present when you retrieved the entry from the server. Otherwise the ignored elements will be deleted. The Google Data API client libraries all handle this correctly, so if you're using one of the libraries you're all set.

Profile photo management

Retrieving a profile's photo

To retrieve a profile's photo, send an authorized GET request to the profile's photo link URL.

The URL is of the form:

https://www.google.com/m8/feeds/photos/profile/domainName/userName

With the appropriate values in place of domainName and userName.

The photo link can be retrieve from the profile entry returned by the API:

<entry ...>
  <id>
    http://www.google.com/m8/feeds/profiles/domain/domainName/full/userName
  </id>
  ...
  <link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*'
    href='https://www.google.com/m8/feeds/photos/profile/domainName/userName'
    gd:etag='photoEtag'/>
  ...
</entry>

Note: If a profile does not have a photo, then the photo link element has no gd:etag attribute.

Upon success, the server responds with an HTTP 200 OK status code and the photo data bytes.

Protocol

Request:

GET /m8/feeds/photos/media/default/profileId

Response:

HTTP/1.1 200 OK
Content-Type: image/*
...
[Photo data bytes]

Java

public static void downloadPhoto(ContactsService service, URL profileURL)
    throws ServiceException, IOException {
  ContactEntry entry = service.getEntry(profileURL,  ContactEntry.class);

  Link photoLink = entry.getContactPhotoLink();
  if (photoLink != null) {
    InputStream in = service.getStreamFromLink(photoLink);
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    RandomAccessFile file = new RandomAccessFile("test.jpg", "rw");
    byte[] buffer = new byte[4096];
    for (int read = 0; (read = in.read(buffer)) != -1;
        out.write(buffer, 0, read));
    file.write(out.toByteArray());
    file.close();
  }
}

.NET

public static void DownloadPhoto(ContactsRequest cr, Uri profileURL)
{
  Contact profile = cr.Retrieve<Contact>(profileURL);

  Stream photoStream = cr.GetPhoto(profile);
  FileStream outStream = File.OpenWrite("test.jpg");
  byte[] buffer = new byte[photoStream.length];

  photoStream.Read(buffer, 0, photoStream.length);
  outStream.Write(buffer, 0, photoStream.length);
  photoStream.Close();
  outStream.Close();
}

Python

def download_photo(gd_client, profile_url):
  profile = gd_client.GetProfile(profile_url)

  hosted_image_binary = gd_client.GetPhoto(profile_entry)
  image_file = open('test.jpg', 'wb')
  image_file.write(hosted_image_binary)
  image_file.close()

Adding/Updating a profile's photo

To add or update a photo for a profile, send an authorized PUT request to the profile's photo URL.

The URL is of the form:

https://www.google.com/m8/feeds/photos/profile/domainName/userName

With the appropriate values in place of domainName and userName.

The photo link can be retrieve from the profile entry returned by the API:

<entry ...>
  <id>
    http://www.google.com/m8/feeds/profiles/domain/domainName/full/userName
  </id>
  ...
  <link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*'
    href='https://www.google.com/m8/feeds/photos/profile/domainName/userName'
    gd:etag='photoEtag'/>
  ...
</entry>

To ensure that the request sent to the API doesn't overwrite another client's changes, the profile entry's Etag should be provided in the request header.

If-Match: Etag

If the Etag is outdated, the server responds with an HTTP 412 Precondition Failed status code.

Note: the special Etag value * can be used to bypass this verification and process the update regardless of updates from other clients.

For more information about ETags, see the Google Data APIs reference guide.

If a profile does not have a photo, then the photo link element has no gd:etag attribute and no If-match is required.

Upon success, the server responds with an HTTP 200 OK status code.

Protocol

Request:

PUT /m8/feeds/photos/media/default/profileId
If-match: Etag
Content-Type: image/*
...
[Photo data bytes]

Response:

HTTP/1.1 OK
...

Java

public static void updateProfilePhoto(ContactsService myService, URL profileURL, byte[] photoData)
    throws ServiceException, IOException {
  ContactEntry profile = myService.getEntry(profileURL, ContactEntry.class);
  Link photoLink = profile.getContactPhotoLink();
  URL photoUrl = new URL(photoLink.getHref());

  GDataRequest request = myService.createRequest(GDataRequest.RequestType.UPDATE,
      photoUrl, new ContentType("image/jpeg"));

  request.setEtag(photoLink.getEtag());

  OutputStream requestStream = request.getRequestStream();
  requestStream.write(photoData);

  try {
    request.execute();
  } catch (PreconditionFailedException e) {
    // Etags mismatch: handle the exception.
  }
}

.NET

public static void UpdateProfilePhoto(ContactsRequest cr, Uri profileURL, Stream photoStream)
{
  Contact profile = cr.Retrieve<Contact>(profileURL);

  try
  {
    cr.SetPhoto(profile, photoStream);
  }
  catch (GDataVersionConflictException e)
  {
    // Etags mismatch: handle the exception.
  }
}

Python

def update_profile_photo(gd_client, profile_url, media_object):
  profile = gd_client.GetProfile(profile_url)

  try:
    gd_client.ChangeePhoto(media_object, profile)
  except gdata.client.RequestError, e:
    if e.status == 412:
      # Etags mismatch: handle the exception.
      pass

Deleting a profile's photo

To delete a profile's photo, send an authorized DELETE request to the profile's photo URL.

The URL is of the form:

https://www.google.com/m8/feeds/photos/profile/domainName/userName

With the appropriate values in place of domainName and userName.

The photo link can be retrieve from the profile entry returned by the API:

<entry ...>
  <id>
    http://www.google.com/m8/feeds/profiles/domain/domainName/full/userName
  </id>
  ...
  <link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*'
    href='https://www.google.com/m8/feeds/photos/profile/domainName/userName'
    gd:etag='photoEtag'/>
  ...
</entry>

To ensure that the request sent to the API doesn't overwrite another client's changes, the profile entry's Etag should be provided in the request header.

If-Match: Etag

If the Etag is outdated, the server responds with an HTTP 412 Precondition Failed status code.

Note: the special Etag value * can be used to bypass this verification and process the update regardless of updates from other clients.

For more information about ETags, see the Google Data APIs reference guide.

If a profile does not have a photo, then the photo link element has no gd:etag.

Upon success, the server responds with an HTTP 200 OK status code.

Protocol

Request:

PUT /m8/feeds/photos/media/default/profileId
If-match: Etag
Content-Type: image/*
...
[Photo data bytes]

Response:

HTTP/1.1 OK
...

Java

public static void deleteProfilePhoto(ContactsService myService, URL profileURL)
    throws ServiceException, IOException {
  ContactEntry profile = myService.getEntry(profileURL, ContactEntry.class);
  Link photoLink = profile.getContactPhotoLink();
  URL photoUrl = new URL(photoLink.getHref());
  try {
    service.delete(photoUrl, photoLink.getEtag());
  } catch (PreconditionFailedException e) {
    // Etags mismatch: handle the exception.
  }
}

.NET

public static void UpdateProfilePhoto(ContactsRequest cr, Uri profileURL)
{
  Contact profile = cr.Retrieve<Contact>(profileURL);
  try
  {
    cr.Delete(profile.PhotoUri, profile.PhotoETag);
  }
  catch (GDataVersionConflictException e)
  {
    // Etags mismatch: handle the exception.
  }
}

Python

def update_profile_photo(gd_client, profile_url):
  profile = gd_client.GetProfile(profile_url)
  try:
    gd_client.DeletePhoto(profile)
  except gdata.client.RequestError, e:
    if e.status == 412:
      # Etags mismatch: handle the exception.
      pass

Batch operations

If you're performing a lot of operations, the time it takes to send and and receive all those HTTP messages can really add up, making your app slow and unresponsive. With batch requests you can have the server perform multiple operations with a single HTTP request. The basic idea is that you create a profiles feed and add an entry for each operation you want to perform.

Since profiles can't be created or deleted, batch requests are limited to query and update requests.

Batch requests are limited to 100 operations at a time. You can find more information about batch operations in the Google Data APIs Batch Processing documentation.

To send a batch request for operations on contacts, send an authorized POST request to the contacts batch feed URL with the batch feed data in the body:

https://www.google.com/m8/feeds/profiles/domain/domainName/full/batch

With the appropriate value in place domainName.

Upon success, the server responds with an HTTP 200 OK status code and the batch feed containing single operation's status code.

Protocol

Request:

POST /m8/feeds/profiles/domain/domainName/full/batch
...

<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom'
      xmlns:gContact='http://schemas.google.com/contact/2008'
      xmlns:gd='http://schemas.google.com/g/2005'
      xmlns:batch='http://schemas.google.com/gdata/batch'>
  <entry>
    <batch:id>retrieve</batch:id>
    <batch:operation type='query'/>
    <id>https://www.google.com/m8/feeds/profiles/domain/domainName/full/retrieveUserName</id>
  </entry>
  <entry gd:etag='updateProfileEtag'>
    <batch:id>update</batch:id>
    <batch:operation type='update'/>
    <category scheme='http://schemas.google.com/g/2005#kind'
      term='http://schemas.google.com/contact/2008#profile' />
    <link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*'
      href='https://www.google.com/m8/feeds/photos/profile/domainName/updateUserName'
      gd:etag='span class="apiparam">photoEtag' />
    <link rel='self' type='application/atom+xml'
       href='https://www.google.com/m8/feeds/profiles/domain/domainName/updateUserName' />
    <link rel='edit' type='application/atom+xml'
       href='https://www.google.com/m8/feeds/profiles/domain/domainName/updateUserName' />
    <title>John Doe</title>
    <gd:name>
      <gd:givenName>John</gd:givenName>
      <gd:familyName>Doe</gd:familyName>
    </gd:name>
    <gContact:status indexed='false' / >
  </entry>
</feed>

Response:

HTTP/1.1 200 OK
Content-Type: application/atom+xml;
...

<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom'
      xmlns:gContact='http://schemas.google.com/contact/2008'
      xmlns:gd='http://schemas.google.com/g/2005'
      xmlns:batch='http://schemas.google.com/gdata/batch'>
  <entry gd:etag='retrieveProfileEtag'>
    <batch:id>retrieve</batch:id>
    <batch:operation type='query'/>
    <batch:status code='200' reason='Success'/>
    <category scheme='http://schemas.google.com/g/2005#kind'
      term='http://schemas.google.com/contact/2008#profile' />
    <link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*'
      href='https://www.google.com/m8/feeds/photos/profile/domainName/retrieveUserName'
      gd:etag='span class="apiparam">photoEtag' />
    <link rel='self' type='application/atom+xml'
       href='https://www.google.com/m8/feeds/profiles/domain/domainName/retrieveUserName' />
    <link rel='edit' type='application/atom+xml'
       href='https://www.google.com/m8/feeds/profiles/domain/domainName/retrieveUserName' />
    <title>Elizabeth Bennet</title>
    <gd:name>
      <gd:givenName>Elizabeth</gd:givenName>
      <gd:familyName>Bennet</gd:familyName>
    </gd:name>
    <gd:structuredPostalAddress rel='http://schemas.google.com/g/2005#work'>
      <gd:formattedAddress>Longbourne, Nr. Meryton, Hertfordshire, England</gd:formattedAddress>
    </gd:structuredPostalAddress>
    <gContact:status indexed='true' / >
  <entry gd:etag='newUpdateContactEtag'>
    <batch:id>update</batch:id>
    <batch:operation type='update'/>
    <batch:status code='200' reason='Success'/>
    <updated>2011-05-11T09:30:00.000Z</updated>
    <category scheme='http://schemas.google.com/g/2005#kind'
      term='http://schemas.google.com/contact/2008#profile' />
    <link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*'
      href='https://www.google.com/m8/feeds/photos/profile/domainName/updateUserName'
      gd:etag='span class="apiparam">photoEtag' />
    <link rel='self' type='application/atom+xml'
       href='https://www.google.com/m8/feeds/profiles/domain/domainName/updateUserName' />
    <link rel='edit' type='application/atom+xml'
       href='https://www.google.com/m8/feeds/profiles/domain/domainName/updateUserName' />
    <title>John Doe</title>
    <gd:name>
      <gd:givenName>John</gd:givenName>
      <gd:familyName>Doe</gd:familyName>
    </gd:name>
    <gContact:status indexed='false' / >
  </entry>
</feed>

Java

public static ContactFeed executeBatchRequest(ContactsService myService)
    throws ServiceException, IOException {
  // Feed that holds all the batch request entries.
  ContactFeed requestFeed = new ContactFeed();

  // Create a ContactEntry for the retrieve request.
  ContactEntry retrieveContact = new ContactEntry();
  retreiveContact.setId("https://www.google.com/m8/feeds/profiles/domain/domainName/full/retrieveUserName");
  BatchUtils.setBatchId(retrieveContact, "retrieve");
  BatchUtils.setBatchOperationType(retrieveContact, BatchOperationType.QUERY);

  // Retrieve the ContactEntry to update.
  ContactEntry updateContact =
      myService.getEntry(new URL("https://www.google.com/m8/feeds/profiles/domain/domainName/full/updateUserName"),
                         ContactEntry.class);
  updateContact.getStatus().setIndexed(false);
  BatchUtils.setBatchId(updateContact, "update");
  BatchUtils.setBatchOperationType(updateContact, BatchOperationType.UPDATE);

  // Insert the entries to the batch feed.
  requestFeed.getEntries().add(retrieveContact);
  requestFeed.getEntries().add(updateContact);

  // Submit the batch request to the server.
  ContactFeed responseFeed =
      myService.batch(new URL("https://www.google.com/m8/feeds/profiles/domain/domainName/full/batch"),
                      requestFeed);

  // Check the status of each operation.
  for (ContactEntry entry : responseFeed.getEntries()) {
    String batchId = BatchUtils.getBatchId(entry);
    BatchStatus status = BatchUtils.getBatchStatus(entry);
    System.out.println(batchId + ": " + status.getCode() + " (" + status.getReason() + ")");
  }
  return responseFeed;
}

.NET

public static Feed<Contact> ExecuteBatchRequest(ContactsRequest cr)
{
  // List that holds the batch request entries.
  List<Contact> requestFeed = new List<Contact>();

  // Create a Contact entry for the retrieve request.
  Contact retrieveContact = new Contact();
  retrieveContact.Id = "https://www.google.com/m8/feeds/profiles/domain/domainName/full/retrieveUserName";
  retrieveContact.BatchData = new GDataBatchEntryData("retrieve", GDataBatchOperationType.query);

  // Retrieve the Contact entry to update.
  Contact updateContact = cr.Retrieve<Contact>(
      new Uri("https://www.google.com/m8/feeds/profiles/domain/domainName/full/updateUserName"));
  updateContact.ContactEntry.Status.Indexed = False;
  updateContact.BatchData = new GDataBatchEntryData("update", GDataBatchOperationType.update);

  // Insert the entries to the batch feed.
  requestFeed.Add(retrieveContact);
  requestFeed.Add(updateContact);

  // Submit the batch request to the server.
  Feed<Contact> responseFeed =
      cr.Batch(requestFeed, new Uri("https://www.google.com/m8/feeds/profiles/domain/domainName/full/batch"),
               GDataBatchOperationType.Default);

  // Check the status of each operation.
  foreach (Contact entry in responseFeed.Entries)
  {
    Console.WriteLine(entry.BatchData.Id + ": " + entry.BatchData.Status.Code + " (" + entry.BatchData.Status.Reason + ")");
  }
  return responseFeed;
}

Python

def execute_batch_request(gd_client):
  # Feed that holds the batch request entries.
  request_feed = gdata.contacts.data.ContactsFeed()

  # Create a ContactEntry for the retrieve request.
  retrieve_contact = gdata.contacts.data.ContactEntry()
  retrieve_contact.id = atom.data.Id(
      text='https://www.google.com/m8/feeds/profiles/domain/domainName/full/retrieveUserName')

  # Retrieve the ContactEntry to update.
  update_contact = gd_client.GetContact('https://www.google.com/m8/feeds/profiles/domain/domainName/full/updateUserName')
  update_contact.status.indexed = 'false'

  # Insert the entries to the batch feed.
  request_feed.AddQuery(entry=retrieve_contact, batch_id_string='retrieve')
  request_feed.AddUpdate(entry=update_contact, batch_id_string='update')

  # submit the batch request to the server.
  response_feed = gd_client.ExecuteBatch(request_feed,
                                         'https://www.google.com/m8/feeds/profiles/domain/domainName/full/batch')

  for entry in response_feed.entry:
    print '%s: %s (%s)' % (entry.batch_id.text, entry.batch_status.code, entry.batch_status.reason)

  return response_feed

Note: update requests require gd:etag attributes to avoid overwriting another client's changes.

Back to top

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.