You want a contact picker that pulls from a user’s Google or Outlook address book. Doing it natively means two integrations: Google’s People API with its own OAuth scopes and people.connections.list paging, and Microsoft Graph with GET /me/contacts and a different permission model. The field names don’t match, and neither covers the other.
The Nylas Contacts API returns both through one endpoint with one schema. The same request reads a Gmail address book and an Outlook one, so your picker code stops caring which provider a user signed in with.
Why use the Nylas Contacts API instead of native contact APIs?
Section titled “Why use the Nylas Contacts API instead of native contact APIs?”Native contact APIs are provider-specific: Google exposes contacts through the People API and Microsoft through Graph, each with its own auth, field names, and paging. A multi-provider app maintains both and reconciles the differences by hand. Nylas normalizes them into one contact object, so a single request and a single parser cover all 3 providers: Google, Microsoft, and Exchange.
The table contrasts the native setup with the unified call.
| Property | Google People API | Microsoft Graph | Nylas Contacts API |
|---|---|---|---|
| Endpoint | people.connections.list | GET /me/contacts | GET /v3/grants/{id}/contacts |
| Schema | Google field names | Graph field names | One unified contact object |
| Providers | Google only | Microsoft only | Google, Microsoft, EWS |
| Address sources | Connections | Default folder | Address book, domain, inbox |
How do I fetch contacts from a user’s account?
Section titled “How do I fetch contacts from a user’s account?”Send a GET request to /v3/grants/{grant_id}/contacts. Nylas returns the user’s address book as an array of contact objects with a single schema, whether the account is Gmail, Outlook, or Exchange. Each object carries the full contact (emails, phone_numbers, job_title, company_name, and groups), so one parser handles all 3 providers.
The request below lists contacts for a grant. The response includes a request_id and a data array of contacts.
curl --compressed --request GET \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/contacts' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json'{ "request_id": "1", "data": [ { "birthday": "1960-12-31", "company_name": "Nylas", "emails": [ { "type": "work", }, { "type": "home", } ], "given_name": "Leyah", "grant_id": "<NYLAS_GRANT_ID>", "groups": [{ "id": "starred" }, { "id": "friends" }], "id": "<CONTACT_ID>", "im_addresses": [ { "type": "jabber", "im_address": "jabber_at_leyah" }, { "type": "msn", "im_address": "leyah.miller" } ], "job_title": "Software Engineer", "manager_name": "Nyla", "middle_name": "Allison", "nickname": "Allie", "notes": "Loves ramen", "object": "contact", "office_location": "123 Main Street", "phone_numbers": [ { "type": "work", "number": "+1-555-555-5555" }, { "type": "home", "number": "+1-555-555-5556" } ], "physical_addresses": [ { "type": "work", "street_address": "123 Main Street", "postal_code": "94107", "state": "CA", "country": "US", "city": "San Francisco" }, { "type": "home", "street_address": "123 Main Street", "postal_code": "94107", "state": "CA", "country": "US", "city": "San Francisco" } ], "picture_url": "https://example.com/picture.jpg", "source": "address_book", "surname": "Miller", "web_pages": [ { "type": "work", "url": "<WEBPAGE_URL>" }, { "type": "home", "url": "<WEBPAGE_URL>" } ] } ], "next_cursor": "2"}import Nylas from "nylas";
const nylas = new Nylas({ apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",});
async function fetchContacts() { try { const identifier = "<NYLAS_GRANT_ID>"; const contacts = await nylas.contacts.list({ identifier, queryParams: {}, });
console.log("Recent Contacts:", contacts); } catch (error) { console.error("Error fetching drafts:", error); }}
fetchContacts();from nylas import Client
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>")
grant_id = "<NYLAS_GRANT_ID>"
contacts = nylas.contacts.list( grant_id,)
print(contacts)For create, update, and delete operations plus the full field reference, see Manage contacts with the Contacts API.
How do I autocomplete email recipients?
Section titled “How do I autocomplete email recipients?”Set the source query parameter to inbox to surface everyone the user has emailed, not only the people saved in their address book. Nylas recognizes 3 contact sources: address_book (saved contacts), domain (the organization directory), and inbox (participants from messages). The inbox source is what powers a recipient picker that suggests real correspondents.
The request below combines the inbox source with the email filter to narrow suggestions as the user types.
curl --request GET \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/contacts?source=inbox&email=jane' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'The inbox source needs an extra scope: contacts.other.readonly on Google and People.Read on Microsoft. It works on those 2 providers only; Exchange (EWS) doesn’t support the inbox source, so build EWS recipient pickers on the email filter instead, which works the same across all 3 providers. The scopes reference lists the exact strings.
How do I keep contacts in sync?
Section titled “How do I keep contacts in sync?”Subscribe a webhook to the contact.updated and contact.deleted triggers, the 2 contact events Nylas emits. Your endpoint receives a notification whenever a contact changes or is removed, so you mirror the change instead of re-listing the whole address book. There’s no contact.created trigger; a brand-new contact arrives as a contact.updated notification.
One caveat worth planning for: Google has no push API for contacts, so Nylas polls Google accounts every 5 minutes and the webhook fires after that interval rather than instantly. Microsoft delivers changes faster. See Get real-time updates with webhooks for the subscription setup.
Things to know about contacts across providers
Section titled “Things to know about contacts across providers”Contact behavior differs enough between providers that a few limits are worth knowing before you build. Microsoft Graph caps a contact at 3 email addresses and leaves the email type as null by default, so don’t rely on work or home labels for Outlook contacts. Exchange (EWS) goes further: it can’t add contacts to groups and drops the suffix field entirely.
Group filtering through the group query parameter works on Google and Microsoft but isn’t supported on EWS. Google’s 5-minute contact polling also means newly added Google contacts can take a few minutes to appear. For the complete per-provider list, see the Contacts limitations and Google’s People API documentation.
What’s next
Section titled “What’s next”- Manage contacts with the Contacts API for create, update, delete, and the full schema
- Contacts API scopes for the exact Google and Microsoft scope strings
- Get real-time updates with webhooks to keep contacts in sync
- Connect user accounts with OAuth to authorize contact access