# How to list iCloud contacts

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

Apple doesn't ship a REST contacts API. iCloud exposes its address book over CardDAV, the same WebDAV-based protocol it uses for calendars, and it gates access behind an app-specific password rather than OAuth. So instead of an `events.insert`-style endpoint, you're writing CardDAV `PROPFIND` and `REPORT` requests against `contacts.icloud.com` and parsing vCard payloads by hand.

This recipe reads an iCloud address book through one call to the Nylas Contacts API. You get the same contact schema returned for Gmail, Outlook, and Exchange accounts, so a single parser handles every provider and you never touch CardDAV or vCard directly.

## Why use Nylas instead of iCloud CardDAV directly?

CardDAV is a sync protocol, not a query API, so reading contacts the native way means more plumbing than a contact picker should need. One Nylas request replaces a CardDAV handshake plus vCard parsing across all 6 supported providers. These 3 differences from a typical REST integration are where the work piles up:

- **No OAuth, no scopes.** iCloud authenticates with an app-specific password the user generates at appleid.apple.com. There's no consent screen and no scope list, so you store and rotate that credential yourself unless a grant holds it for you.
- **vCard parsing.** CardDAV `REPORT` requests return vCard 3.0 text, not JSON. You parse `FN`, `EMAIL`, `TEL`, and `ORG` lines and reconcile them into objects. The API does this and hands back JSON fields like `emails`, `phone_numbers`, and `company_name`.
- **One schema across providers.** The same `GET /v3/grants/{grant_id}/contacts` request reads an iCloud, Gmail, Outlook, or Exchange address book, so your picker code stays provider-agnostic.

## Before you begin

To list iCloud contacts you need a connected iCloud account, and that connection depends on 2 things Apple requires: an iCloud connector in your project and an app-specific password from the user. iCloud doesn't use OAuth scopes, so there's no permission list to request.

- A [Nylas application](/docs/v3/getting-started/) with a valid API key
- An iCloud connector added to that application
- A [grant](/docs/v3/auth/) for an iCloud account authorized with an app-specific password


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


### Create an iCloud app password

Apple requires an app-specific password to connect any third-party app, and it must be generated by the user because Apple offers no API for it. Direct the user to [appleid.apple.com](https://appleid.apple.com/account/home), have them sign in, and walk them through Apple's [app-specific password steps](https://support.apple.com/en-us/102654). They enter that 16-character password during auth instead of their main Apple ID password. The full connector and Bring Your Own Authentication flow lives in the [iCloud provider guide](/docs/provider-guides/icloud/).

## List iCloud contacts

Send a `GET` request to `/v3/grants/{grant_id}/contacts` with the grant ID for the connected iCloud account. The response is a `data` array of contact objects plus a `request_id`, and a single page returns 30 contacts by default and up to 200 with the `limit` parameter. Each object carries `emails`, `phone_numbers`, `job_title`, `company_name`, and `groups` populated from the iCloud address book.

The samples below list contacts for one grant and print the unified response.

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

## List iCloud contacts from the terminal

iCloud exposes contacts over CardDAV, and each user must create a 16-character app-specific password before any client connects. The [Nylas CLI](https://cli.nylas.com/docs/commands) reads them once that grant exists: `nylas contacts list` returns the account's contacts straight from your terminal, with no CardDAV client to build or sync logic to maintain.


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.


Like other IMAP-style providers, an iCloud contact holds at most one email and one phone number, so don't expect the multi-value fields a Google contact carries. Listing returns 50 contacts by default. Filter to the saved set with `nylas contacts list --source address_book`, which skips auto-collected senders.

## Filter iCloud contacts

Three query parameters narrow an iCloud contact list: `email`, `phone_number`, and `source`. iCloud supports the `phone_number` filter, which Microsoft doesn't, so you can match a contact by digits as well as by address. The `source` parameter picks between saved contacts (`address_book`) and the auto-generated entries Nylas derives from message participants (`inbox`). The `recurse` parameter is Microsoft-only, so it has no effect on an iCloud grant.

This curl request returns contacts whose email contains `jane` from the user's saved iCloud address book. Swap `email` for `phone_number` to match a number instead.

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

iCloud is one of only 2 providers that accept a compound `source` filter, so you can pass `source=address_book,inbox` to merge saved and auto-generated contacts in a single request.

## Things to know about iCloud contacts

iCloud contacts arrive as vCard 3.0 over CardDAV, and that single fact shapes the 4 details below: the protocol underneath, the app-password auth model, compound source filtering, and the 90-day cache that governs sync timing. Read these before you wire the address book into a picker or a sync job.

### CardDAV is the protocol underneath

Nylas reads iCloud contacts over CardDAV, the address-book extension to WebDAV defined in [RFC 6352](https://www.rfc-editor.org/rfc/rfc6352), rather than a JSON REST API. CardDAV speaks `PROPFIND` and `REPORT` requests and returns vCard 3.0 payloads, which the API normalizes into the unified contact object. Two effects fall out of this. First, iCloud contacts have no concept of system labels like Gmail's `starred`, so the `groups` array reflects only CardDAV address-book collections. Second, contact IDs are stable CardDAV resource URLs, so they survive across syncs and are safe to store as foreign keys.

### Auth uses an app-specific password, not OAuth scopes

Every other major contacts provider uses OAuth, but iCloud uses an app-specific password the user generates manually. There's no scope list and no admin consent step, which removes a verification hurdle that Google's sensitive `contacts` scopes and Microsoft's `Contacts.Read` admin consent both impose. The tradeoff is credential management: a user must regenerate the 16-character password if they reset their Apple ID password or revoke the app, and the grant moves to an error state until they re-authenticate. Surface a reconnect prompt so a stale password doesn't silently stop contact reads.

### Compound source filters are an iCloud advantage

The `source` parameter accepts a comma-separated list on iCloud and IMAP only, the 2 providers where Nylas reads contacts from both the CardDAV address book and message participants. Pass `source=address_book,inbox` to return saved contacts and auto-generated ones in a single call, which Google and Microsoft can't do in one request. This is useful for recipient autocomplete, where you want both the user's saved contacts and the people they've recently emailed ranked together. Filtering on `source=address_book` alone returns only the entries the user explicitly saved in the Contacts app.

### Sync timing and the 90-day cache

Nylas caches iCloud data for 90 days and reads new contact changes from CardDAV on its sync schedule rather than in real time, so a contact added on an iPhone may take a short interval to surface through the list call. iCloud also doesn't emit auto-generated "other contacts" the way Gmail builds a separate collection, so the `inbox` source is derived from message participants instead of an Apple-managed list. Apple enforces account-level limits documented in its [iCloud system limits](https://support.apple.com/en-gb/102198), so if you sync many accounts on a schedule, stagger the jobs rather than firing them at once.

## Paginate through results

The list endpoint returns 30 contacts per page by default, up to a maximum of 200, and uses cursor pagination through the `page_token` query parameter. Each response includes a `next_cursor` value when more contacts exist; pass it as `page_token` on the next request to fetch the following page. Loop until a response omits `next_cursor`, which signals the last page.

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

## What's next

- [How to list Google contacts](/docs/cookbook/contacts/list-contacts-google/) for the People API equivalent and the `source` field
- [How to list Microsoft contacts](/docs/cookbook/contacts/list-contacts-microsoft/) for Graph contact folders and admin consent
- [How to search contacts](/docs/cookbook/contacts/search-contacts/) to match by email, phone, group, or source
- [iCloud provider guide](/docs/provider-guides/icloud/) for the connector, app passwords, and Bring Your Own Authentication flow
- [Manage contacts with the Contacts API](/docs/v3/email/contacts/) for create, update, delete, and the full schema