# How to list directory contacts

Source: https://developer.nylas.com/docs/cookbook/contacts/list-directory-contacts/

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](/docs/cookbook/contacts/list-contacts-google/) and [Microsoft](/docs/cookbook/contacts/list-contacts-microsoft/) cover that default path.

## What's the source parameter?

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](/docs/cookbook/contacts/list-contacts-google/) and [Microsoft](/docs/cookbook/contacts/list-contacts-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.

| Source         | What it returns                          | Google scope               | Microsoft scope | Provider notes                          |
| :------------- | :--------------------------------------- | :------------------------- | :-------------- | :-------------------------------------- |
| `address_book` | Saved personal contacts (default)        | `contacts.readonly`        | `Contacts.Read` | All 6 providers                         |
| `domain`       | Organization directory / global list     | `directory.readonly`       | `People.Read`   | Google Workspace and Microsoft 365      |
| `inbox`        | Auto collected from messages             | `contacts.other.readonly`  | `People.Read`   | Not 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.

## List organization directory contacts

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.

```bash
curl --compressed --request GET \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/contacts?source=domain&limit=50' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

```json
{
  "request_id": "1",
  "data": [
    {
      "birthday": "1960-12-31",
      "company_name": "Nylas",
      "emails": [
        {
          "type": "work",
          "email": "leyah.miller@example.com"
        },
        {
          "type": "home",
          "email": "leyah@example.com"
        }
      ],
      "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"
}


```

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.

```js [listDirectory-Node.js]
const contacts = await nylas.contacts.list({
  identifier: "<NYLAS_GRANT_ID>",
  queryParams: { source: "domain", limit: 50 },
});
console.log(contacts.data);
```

```python
contacts = nylas.contacts.list(
    "<NYLAS_GRANT_ID>",
    query_params={"source": "domain", "limit": 50},
)
print(contacts.data)
```

## List auto collected inbox contacts

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.

```bash
curl --compressed --request GET \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/contacts?source=inbox&limit=50' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

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.

```python
contacts = nylas.contacts.list(
    "<NYLAS_GRANT_ID>",
    query_params={"source": "inbox", "limit": 50},
)
print(contacts.data)
```

## List directory contacts from the terminal

The [Nylas CLI](https://cli.nylas.com/docs/commands) 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.

```bash
# 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`](https://cli.nylas.com/docs/commands/contacts-list) command reference.

## Things to know about directory access

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.

### Directory and inbox need broader scopes

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](/docs/dev-guide/scopes/) for how to request them during auth.

### What the directory returns

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.

### Sync timing and pagination

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.

## Paginate through results

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.

```bash
curl --compressed --request GET \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/contacts?source=domain&limit=200&page_token=<NEXT_CURSOR>' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

## What's next

- [How to list Google contacts](/docs/cookbook/contacts/list-contacts-google/) for the default saved address book and People API scopes
- [How to list Microsoft contacts](/docs/cookbook/contacts/list-contacts-microsoft/) for Graph admin consent and Outlook contacts
- [How to search contacts](/docs/cookbook/contacts/search-contacts/) to filter by email, phone number, or group
- [Using scopes to request user data](/docs/dev-guide/scopes/) for the directory and inbox OAuth scopes
- [Contacts API reference](/docs/reference/api/contacts/) for every endpoint, parameter, and field