Skip to content
Skip to main content

How to list directory contacts

Last updated:

A user’s saved address book is only one of three places contacts live. The other two are the organization directory (the company-wide global address list every coworker shares) and the inbox (people auto collected from email the user has exchanged). The Contacts API reaches all three through a single source query parameter, and most teams never realize the directory is one request away.

This recipe shows how to read directory and auto collected contacts with the Nylas Contacts API by switching the source value, and which OAuth scope each source needs. If you only want a user’s personal saved contacts, the provider-specific guides for Google and Microsoft cover that default path.

The source parameter on GET /v3/grants/{grant_id}/contacts chooses which set of contacts to return: address_book (the user’s saved contacts, the default), domain (the organization directory shared across the tenant), or inbox (contacts auto generated from messages). One enum value swaps the underlying provider resource, so the same request reads a personal list or a 10,000-person company directory.

Each value maps to a different provider resource and a different OAuth scope. The default, address_book, is what the Google and Microsoft list guides cover, so this page focuses on the other two. The table below compares all 3 sources, their scope requirements, and provider support.

SourceWhat it returnsGoogle scopeMicrosoft scopeProvider notes
address_bookSaved personal contacts (default)contacts.readonlyContacts.ReadAll 6 providers
domainOrganization directory / global listdirectory.readonlyPeople.ReadGoogle Workspace and Microsoft 365
inboxAuto collected from messagescontacts.other.readonlyPeople.ReadNot supported on EWS

Compound source filters, such as source=address_book,inbox, are supported for IMAP and iCloud only. The other 4 providers accept one source value per request.

The directory holds every coworker in the tenant, not just the people one user saved. Pass source=domain to GET /v3/grants/{grant_id}/contacts to read that shared global address list, which on Google Workspace and Microsoft 365 can run to tens of thousands of entries. A single page returns up to 30 contacts by default, so directory reads almost always paginate.

The request below lists directory contacts for one grant. It returns the same data array and contact schema as a personal list, so your rendering code doesn’t change when you switch sources.

Both SDKs accept source through query_params. The Node.js and Python calls below request the organization directory and cap the page at 50 contacts, the same setup you’d use to populate an internal company picker.

The inbox source returns contacts the provider generates from email a user has sent or received, not anyone they saved by hand. Pass source=inbox to surface these auto collected entries, which power “people you email often” suggestions. A heavy email user can hold several thousand inbox contacts against only 200 or so saved ones, so this source is noisier than the address book.

The request below reads auto collected contacts for one grant. EWS doesn’t support inbox at all, so on Exchange accounts this returns an error rather than an empty list. The page size still defaults to 30 contacts here.

The SDK call mirrors the curl request. The Python snippet below requests inbox contacts; swap inbox for address_book,inbox on an IMAP or iCloud grant to merge saved and auto collected entries in one response.

The Nylas CLI reads the organization directory with one filter: nylas contacts list --source domain returns coworkers from the company directory rather than the user’s personal address book, and it’s the fastest way to confirm directory access works for a tenant.

# List directory (coworker) contacts
nylas contacts list --source domain
# Compare with the user's saved contacts
nylas contacts list --source address_book

The domain source maps to the Google Workspace or Microsoft 365 directory, and a large organization can return thousands of entries, so listing returns 50 contacts per page and auto-paginates over 200. See the contacts list command reference.

Reading the directory and inbox sources depends on admin-level scopes that the default address book doesn’t need. The 3 points below cover the scopes, what the directory actually returns, and the sync behavior that shapes a directory read.

The domain source requires the Google directory.readonly scope and the Microsoft People.Read scope; the inbox source needs Google contacts.other.readonly and Microsoft People.Read. These are more permissive than the contacts.readonly and Contacts.Read scopes a personal list uses, and a production Google app must clear OAuth verification before users outside your test list can grant them. On Google Workspace, an admin can also restrict directory sharing tenant-wide, which empties the domain result even when the scope is granted. See Using scopes to request user data for how to request them during auth.

A domain read returns the organization’s global address list: every coworker’s name, email, job title, and department that the tenant exposes. Unlike the personal address book, the user didn’t create these entries, so fields like notes or custom groups are usually empty. Microsoft 365 surfaces the directory through Graph’s people resource, and Google Workspace through its directory people resource, but the Contacts API normalizes both into one schema. Don’t assume every field is populated; directory records are often just a name, email, and title.

Directory contacts sync on the same schedule as everything else on a grant, and Google polls every 5 minutes, so a coworker added moments ago may not appear yet. Because a directory can hold tens of thousands of records and a page caps at 200 contacts, reading a full 10,000-person directory takes at least 50 requests. Cache the result and refresh it on a schedule rather than calling source=domain on every page load; the directory changes far more slowly than a personal address book does.

A directory rarely fits in one page, so follow the cursor to read it in full. Each list response includes a next_cursor value; pass it back as the page_token query parameter to fetch the next page of up to 200 contacts. Repeat until next_cursor is absent, which is the only way to read an entire organization directory.

The request below fetches the next page of directory contacts using a cursor from a prior response. Keep the source=domain filter on every page, since the cursor alone doesn’t carry it.