# How to list Exchange contacts

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

Microsoft announced EWS retirement in 2022, yet plenty of enterprises still run Exchange in their own data centers, and that on-premises address book has no REST API of its own. Pulling contacts out of it natively means hand-building SOAP envelopes, parsing XML responses, and dealing with autodiscovery before you read a single name.

The Nylas Contacts API reads an on-premises Exchange address book over Exchange Web Services (EWS) and returns the same contact schema you get from Google or Outlook. One `GET` request, no SOAP, and your contact code doesn't branch on whether the mailbox lives in the cloud or in a closet down the hall.

## Why use Nylas instead of EWS directly?

EWS is a SOAP-based XML protocol with no native JSON or REST surface. Reading the address book directly means building XML request envelopes, parsing XML responses, decoding SOAP faults, and solving autodiscovery to locate the server first. The 4 hurdles below are where on-premises integrations stall:

- **SOAP and XML.** Every `FindItem` call ships a hand-built XML envelope, and every response needs an XML parser. There's no JSON path.
- **Autodiscovery.** EWS finds its endpoint from the user's email address, and the lookup is frequently misconfigured in enterprise DNS.
- **Authentication.** EWS uses basic or NTLM credentials over TLS, plus 16-character app passwords when two-factor is on, rather than an OAuth redirect.
- **No unified schema.** EWS contact fields look nothing like Graph or Google fields, so a direct integration can't share parsing code with any cloud provider.

The API folds all 4 into one REST call returning one contact object. If you have deep EWS experience and only target on-premises Exchange, integrating directly stays an option.

## Before you begin

To list Exchange contacts you need a connected on-premises mailbox and an EWS connector scoped for contacts. Setup takes 2 pieces: a grant for the Exchange account and the `ews.contacts` scope on the connector your application uses.

- A [Nylas application](/docs/v3/getting-started/) with a valid API key
- A [grant](/docs/v3/auth/) for an on-premises Exchange account
- An EWS connector created with the `ews.contacts` scope

For the full connect-and-grant walkthrough, including autodiscovery and impersonation, see [How to connect Exchange (EWS) accounts](/docs/cookbook/email/connect-exchange-ews/).


> **Info:** 
> **New to Nylas?** Start with the [quickstart guide](/docs/v3/getting-started/) to set up your app and connect a test account before continuing here.


### The ews.contacts scope

EWS defines a separate Nylas scope per data type, and the connector's `scopes` field accepts a single value. For contacts you set it to `ews.contacts`; mail uses `ews.messages` and calendar uses `ews.calendars`. Create a dedicated connector for each data type your app touches, because one connector can't carry mail and contacts at once. The [Exchange on-premises provider guide](/docs/provider-guides/exchange-on-prem/) lists every scope and the dashboard steps to add them.

## List Exchange contacts

Send a `GET` request to `/v3/grants/{grant_id}/contacts` with the grant ID for the on-premises mailbox. The endpoint returns the address book as a `data` array of contact objects plus a `request_id`, and a single page returns up to 30 contacts by default. Each object carries the unified schema, so one parser reads Exchange, Outlook, and Gmail contacts without provider branches.

The request below lists contacts for one grant. The response is the standard Nylas contact object, with `emails`, `phone_numbers`, `job_title`, and `company_name` populated from the Exchange account.

```bash
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'

```

```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"
}


```

```js [listContacts-Node.js SDK]

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();


```

```python [listContacts-Python SDK]

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 complete field reference, see [Manage contacts with the Contacts API](/docs/v3/email/contacts/).

## List Exchange contacts from the terminal

Exchange contacts behave differently from cloud providers: the list endpoint accepts only `source=address_book`, with `source=inbox` and contact groups unsupported. The [Nylas CLI](https://cli.nylas.com/docs/commands) reflects that: `nylas contacts list` returns an Exchange grant's saved contacts, and the source filter has just one valid value.


The Nylas CLI reads contacts from your terminal with the same source model as the Contacts API. After `nylas init` and `nylas auth login`, `contacts list` returns 50 contacts by default and pages through everything automatically once `--limit` goes over 200:

```bash
# List 50 contacts (default)
nylas contacts list

# Only the saved address book, skipping auto-collected senders
nylas contacts list --source address_book --limit 100
```

Every contact carries a source: `address_book` (saved by the user), `inbox` (auto-collected from sent mail), or `domain` (the organization directory). Filter deliberately, because a busy account can hold thousands of `inbox` contacts the user never saved. To find a specific person rather than list everyone, `contacts search` matches on company, email, or phone:

```bash
# Find contacts at a company who have an email on file
nylas contacts search --company "Acme" --has-email
```

See the [`contacts list`](https://cli.nylas.com/docs/commands/contacts-list) and [`contacts search`](https://cli.nylas.com/docs/commands/contacts-search) command reference for every flag.


So on Exchange, `nylas contacts list --source address_book` is the meaningful query; `--source inbox` returns nothing, and contact groups aren't available either. Listing returns 50 contacts by default. EWS contact fields don't match Graph or Google, so treat the normalized output as the canonical shape rather than mapping field by field.

## Filter Exchange contacts

Narrow the result set with query parameters instead of paging the whole address book client-side. On EWS the list endpoint accepts 3 working filters, `email`, `phone_number`, and `source`, but the parameters behave differently than they do on cloud providers: `source=inbox` isn't supported, and the `group` parameter doesn't work at all. That leaves `source=address_book` as the only valid source value for an Exchange grant.

This curl request returns saved contacts whose email contains `jane`. The `phone_number` filter also works on EWS, so you can match a saved phone string the same way.

```bash
curl --request GET \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/contacts?source=address_book&email=jane' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

```js [filterContacts-Node.js SDK]
const contacts = await nylas.contacts.list({
  identifier: grantId,
  queryParams: {
    source: "address_book",
    email: "jane",
  },
});
```

```python [filterContacts-Python SDK]
contacts = nylas.contacts.list(
    grant_id,
    query_params={
        "source": "address_book",
        "email": "jane",
    },
)
```

Don't reach for compound sources like `source=address_book,inbox` on Exchange. The API supports comma-separated sources for IMAP and iCloud only, so on EWS you pass a single source value.

## Things to know about Exchange contacts

Exchange on-premises contacts behave differently from the cloud providers in 5 areas worth knowing before you build: the connection model, the on-prem versus Exchange Online split, the unsupported source and group filters, network reachability, and admin-set throttling. The notes below cover each so an enterprise quirk doesn't surprise you.

### EWS is the on-premises connection model

Nylas reads on-premises Exchange contacts through Exchange Web Services, the SOAP protocol Exchange has shipped since 2007. The API runs autodiscovery to find the server's EWS endpoint from the user's email address, authenticates with the stored credentials, then issues EWS contact operations and normalizes the result into the unified schema. You never touch a SOAP envelope or an EWS field name. Reading contacts requires the `ews.contacts` scope on the connector, the same per-data-type scope model the [connect guide](/docs/cookbook/email/connect-exchange-ews/) describes for mail and calendar.

### On-prem Exchange versus Exchange Online

These are two different worlds, and contacts route through different code in each. A mailbox hosted by Microsoft in the cloud, including Exchange Online, Microsoft 365, and Outlook.com, uses the `microsoft` connector built on Microsoft Graph. A mailbox on a server the organization runs itself uses the `ews` connector. Every mailbox lives in exactly one of these, so if your users are on Exchange Online, follow [How to list Microsoft contacts](/docs/cookbook/contacts/list-contacts-microsoft/) instead. To pull contacts from Gmail or Google Workspace, see [How to list Google contacts](/docs/cookbook/contacts/list-contacts-google/).

### The source parameter and the missing inbox value

EWS supports the `source` parameter, but it accepts only `address_book` for an Exchange grant. The `inbox` value, which surfaces people a user has emailed but never saved, isn't available on EWS, so you can't suggest unsaved recipients from message history the way you can on Google or Microsoft. If you call the endpoint without a `source`, it defaults to `address_book` and returns the saved contacts, which is the behavior you want on Exchange anyway.

### The group parameter doesn't work on EWS

Unlike Outlook contact folders and Google labels, the `group` query parameter isn't supported for EWS at all. You can't filter an Exchange address book to a single Contact Group through the API, and `recurse` is a Microsoft-only parameter that has no effect here either. If your app organizes contacts by group, do the grouping client-side after listing, or store group membership in your own data layer rather than relying on a server-side `group` filter.

### Network reachability gates every read

The Exchange server must accept connections from outside the corporate network with a valid public TLS certificate, over port 443 by default. A mailbox locked behind a VPN, an internal-only firewall rule, or a self-signed certificate can't be reached, so a contact list call fails before it ever queries the address book. If the server sits behind a firewall, allowlist Nylas IP addresses, available on contract plans with static IP routing. Microsoft's [autodiscover reference](https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/autodiscover-for-exchange) documents the endpoint lookup that has to succeed first.

### Throttling is set by the server admin

Exchange rate limits come from a throttling policy the server administrator configures, not from Nylas, so the ceilings vary per deployment. When the server throttles a request, the API surfaces a `Retry-After` header telling you how many seconds to wait, often 30 seconds or more. A single tight sync loop across many mailboxes can trip a strict policy in seconds. Sync contact changes with [webhooks](/docs/v3/notifications/) instead of polling so you stay well under whatever limit the admin set.

## What's next

- [How to connect Exchange (EWS) accounts](/docs/cookbook/email/connect-exchange-ews/) for the connector, scopes, and grant flow
- [How to list Exchange email messages](/docs/cookbook/email/messages/list-messages-ews/) for reading mail from the same on-premises account
- [How to list Microsoft contacts](/docs/cookbook/contacts/list-contacts-microsoft/) for Exchange Online and Microsoft 365 on Microsoft Graph
- [How to list Google contacts](/docs/cookbook/contacts/list-contacts-google/) for the Google People API equivalent
- [Manage contacts with the Contacts API](/docs/v3/email/contacts/) for create, update, delete, and the full schema
- [Exchange on-premises provider guide](/docs/provider-guides/exchange-on-prem/) for server setup, minimum versions, and network requirements