# How to list Microsoft contacts

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

Pulling contacts out of Microsoft 365 the native way means a detour through Azure AD: register an app, request the `Contacts.Read` permission, clear admin consent for enterprise tenants, then handle Graph throttling on top. The address book itself is one `GET` call once you're past that setup.

The Nylas Contacts API skips the Azure registration and the Graph plumbing. One request reads an Outlook address book and returns the same contact schema you'd get from a Google or Exchange account, so your code doesn't branch per provider.

## Why use Nylas instead of the Microsoft Graph API directly?

Microsoft Graph exposes contacts through `GET /me/contacts`, but reaching that endpoint takes real setup work. You register an Azure AD application, request the `Contacts.Read` permission scope, secure admin consent for organization tenants, and run the MSAL token lifecycle yourself. The 4 hurdles below are where most of the time goes:

- **Azure AD app registration**: every Graph integration starts with an app object, a client secret, and redirect URIs configured in the Azure portal.
- **Admin consent**: tenant admins must approve delegated permissions like `Contacts.Read` before users in restricted organizations can connect.
- **Graph permission scopes**: read access needs `Contacts.Read`; read-write needs `Contacts.ReadWrite`, and each scope changes the consent prompt.
- **Throttling**: Graph returns HTTP `429` with a `Retry-After` header once a mailbox exceeds its per-app request budget, so you build backoff logic.

The API normalizes all 4 into one REST call with one contact object. If you already run Graph and only target Microsoft, the native route works fine.

## Before you begin

To list Microsoft contacts you need a connected Outlook or Microsoft 365 account and the right Graph permission on your Azure app. Setup takes 2 pieces: an authenticated grant and the `Contacts.Read` scope approved for the tenant.

- A [Nylas application](/docs/v3/getting-started/) with a valid API key
- A [grant](/docs/v3/auth/) for a Microsoft 365 or Outlook account
- The `Contacts.Read` permission enabled in your Azure AD app registration

For the full Azure AD walkthrough, including admin consent and publisher verification, see the [Microsoft provider guide](/docs/provider-guides/microsoft/).


> **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.


### Microsoft admin consent

Many Microsoft organizations block third-party apps until a tenant admin approves them. If users hit a "Need admin approval" screen during auth, their organization restricts user consent and an admin must grant the `Contacts.Read` permission once for the whole tenant. After that single approval, every user in that organization can connect without seeing the prompt again. Nylas documents the flow in [Configuring Microsoft admin approval](/docs/provider-guides/microsoft/admin-approval/).

## List Microsoft contacts

Send a `GET` request to `/v3/grants/{grant_id}/contacts` with the grant ID for the Outlook account. The endpoint returns the user's address book as a `data` array of contact objects plus a `request_id`. Each object carries the full contact, so a single parser reads all 3 providers (Outlook, Gmail, and Exchange) without provider-specific branches.

The request below lists contacts for one grant. The response is the unified Nylas contact schema, with `emails`, `phone_numbers`, `job_title`, `company_name`, and `groups` populated from the Microsoft 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/).

## Filter Microsoft contacts

Narrow the result set with query parameters instead of listing every contact and filtering client-side. The list endpoint accepts `email`, `phone_number`, `source`, `group`, and `recurse`, and Microsoft supports all 5. The `recurse` parameter is Microsoft-only and descends exactly 1 level into Contact Group subgroups.

This curl request returns contacts whose email contains `jane` from the user's saved address book. Swap `email` for `group` to pull a single Outlook contact folder by its group ID.

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

The `source=inbox` value surfaces people the user has emailed but never saved, which is handy for recipient autocomplete. On Microsoft it needs the extra `People.Read` permission alongside `Contacts.Read`.

## Things to know about Microsoft contacts

Microsoft contacts behave differently from Google's in 5 areas worth knowing before you build: the Graph endpoint mapping, contact folders, categories, the lack of auto-generated contacts, and throttling. The notes below cover each so your integration doesn't trip on an Outlook-specific quirk.

### Graph `/me/contacts` mapping

Behind the scenes, Nylas reads Microsoft contacts through Graph's [`GET /me/contacts`](https://learn.microsoft.com/en-us/graph/api/user-list-contacts) endpoint and normalizes the response into the unified schema. Graph fields like `emailAddresses`, `businessPhones`, and `jobTitle` become `emails`, `phone_numbers`, and `job_title`. You read the normalized object and never touch a Graph field name. The required delegated scope is `Contacts.Read`, the same permission Microsoft lists for that endpoint.

### Contact folders and the `group` parameter

Outlook stores contacts in folders, and each folder maps to a Nylas contact group. Filter to one folder by passing its ID through the `group` query parameter, then add `recurse=true` to include subfolders 1 level deep. Don't hardcode folder names because users rename and create their own. List the groups first to discover the real IDs for an account.

### Outlook categories aren't Nylas groups

Microsoft has two organizing concepts that look similar but aren't. Contact folders map to Nylas groups; Outlook categories (the colored labels like "Red Category") are a separate tagging system that Graph exposes as a `categories` string array on the contact, not as folders. If you need category data, the unified contact object won't carry it the way it carries group membership, so plan around folders rather than categories for grouping.

### No auto-generated "other contacts"

Google quietly builds an "Other contacts" list from people you've emailed, exposed through the `contacts.other.readonly` scope. Microsoft has no equivalent auto-created collection. An Outlook address book holds only contacts the user explicitly saved. To suggest people the user has emailed but never saved, use the `source=inbox` filter, which Nylas derives from message participants rather than from any Microsoft-managed list.

### Graph throttling returns HTTP 429

Microsoft Graph enforces per-app, per-mailbox request limits and answers an over-budget request with HTTP `429 Too Many Requests` and a `Retry-After` header telling you how many seconds to wait. Nylas retries throttled requests automatically, so you don't write backoff logic yourself. If your app polls contacts on a tight loop across many mailboxes, you'll still hit these limits faster. Microsoft documents the behavior in its [throttling guidance](https://learn.microsoft.com/en-us/graph/throttling). Sync contact changes with [webhooks](/docs/v3/notifications/) instead of polling to stay well under the cap.

## What's next

- [List and sync contacts from Google and Outlook](/docs/cookbook/contacts/list-and-sync-contacts/) for the cross-provider overview and sync webhooks
- [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
- [Microsoft provider guide](/docs/provider-guides/microsoft/) for Azure AD setup, admin consent, and publisher verification
- [Connect user accounts with OAuth](/docs/v3/auth/) to authorize contact access