# Apple Contacts API: CardDAV, Contacts framework, REST

Source: https://developer.nylas.com/docs/cookbook/contacts/apple-contacts-api/

Two different things answer to the name "Apple Contacts API," and neither is a web API. One is the Contacts framework, a Swift and Objective-C library that runs inside apps on Apple devices. The other is CardDAV, the protocol iCloud uses to store and sync address books. A server-side service that needs a user's iCloud contacts can only use the second. This page explains both and shows how the Nylas Contacts API puts JSON endpoints in front of iCloud CardDAV.

## Does Apple have a contacts API?

Apple has no public REST API for contacts. It offers the Contacts framework for apps running on iPhone, iPad, Mac, Apple Watch, and Vision Pro, and iCloud stores contacts on a CardDAV server defined by RFC 6352, published in August 2011. Server-side code reaches iCloud contacts through CardDAV or through a service that wraps it.

The split matters when you plan an integration. If your product is an iOS or macOS app, the framework reads contacts on the user's device. If your product is a web app, CRM, or scheduled server job, the device isn't involved, and you need a direct connection to the user's iCloud address book. Apple's own developer documentation confirms the storage model: the [`CNContainerType.cardDAV`](https://developer.apple.com/documentation/contacts/cncontainertype/carddav) container type describes contacts stored on a CardDAV server, with iCloud as the example.

## What's the Apple Contacts framework?

The [Contacts framework](https://developer.apple.com/documentation/contacts) is Apple's native library for reading and formatting the user's contacts from Swift or Objective-C. It runs on the user's device and exposes no network endpoint, so a server can't call it. It has been available since iOS 9.0 and macOS 10.11, when it replaced Apple's older Address Book framework.

Apple describes the framework as optimized for thread-safe, read-only usage, since most apps only read contacts. Its central type, `CNContact`, is an immutable value object with properties such as name, image, and phone numbers. Contacts live in containers, and the `CNContainerType` enum has 4 cases: `local`, `exchange`, `cardDAV` (with iCloud as Apple's example), and `unassigned`.

None of that helps server-side code. To sync iCloud contacts into a web app without shipping an Apple-platform client, you connect to the iCloud CardDAV server using the RFC 6352 protocol from 2011, either yourself or through an API that does it for you.

## How does iCloud CardDAV work?

CardDAV is an extension of WebDAV that stores each contact as a vCard resource on an HTTP server. [RFC 6352](https://www.rfc-editor.org/rfc/rfc6352), published in August 2011, requires servers to support vCard 3.0 and defines the `addressbook-query` and `addressbook-multiget` REPORT methods for searching and batch-fetching contacts. Requests and responses are XML, and each contact body is vCard text.

A working CardDAV client for iCloud has to discover the user's address book collections, issue `PROPFIND` and `REPORT` requests, parse vCard records, and use `ETag` values for conditional writes so it doesn't overwrite a change made on the user's iPhone. Groups are their own vCard records, separate from the address-book containers, and the combined All Contacts view isn't a group at all.

Authentication uses an [app-specific password](https://support.apple.com/en-us/102654). Apple's instructions name mail, contacts, and calendars as the iCloud data that third-party apps sign in to reach. The user's account needs two-factor authentication, and Apple allows up to 25 active app-specific passwords. Changing the main Apple Account password revokes every one of them.

## iCloud CardDAV vs the Nylas Contacts API

The Nylas Contacts API handles CardDAV discovery, vCard parsing, and conditional writes for native iCloud grants, and returns the same contact object it returns for Google, Microsoft, Yahoo, and Exchange. The table compares that with a direct CardDAV client. The API rejects any single vCard larger than 1 MB and any inventory response larger than 8 MB instead of parsing unbounded data.

| Task               | iCloud CardDAV (direct)                          | Nylas Contacts API                                         |
| ------------------ | ------------------------------------------------ | ---------------------------------------------------------- |
| Data format        | XML requests, vCard 3.0 bodies                   | JSON contact objects                                       |
| Listing contacts   | `PROPFIND` and `REPORT` against each collection  | `GET /v3/grants/{grant_id}/contacts`, up to 200 per page   |
| Writing contacts   | `PUT` a vCard with an `If-Match` ETag            | `POST`, `PUT`, and `DELETE` on the Contacts endpoints      |
| Groups             | Group vCard records you parse yourself           | `GET /v3/grants/{grant_id}/contacts/groups`                |
| IDs                | Server `href` paths                              | Opaque IDs starting with `carddav_v1_`                     |
| Change detection   | You poll and diff                                | `contact.updated` and `contact.deleted` notifications      |
| Other providers    | iCloud only                                      | Google, Microsoft, Yahoo, iCloud, and Exchange       |

The notifications are polled, not real-time, and the first successful scan creates a silent baseline. Duplicates are possible, so keep handlers idempotent. The [list iCloud contacts recipe](/docs/cookbook/contacts/list-contacts-icloud/#icloud-contact-notifications) covers delivery behavior in detail.

## How do I list iCloud contacts as JSON?

Listing iCloud contacts through Nylas takes 1 `GET` request to `/v3/grants/{grant_id}/contacts` with a native iCloud grant. Omitting the `source` parameter selects the CardDAV `address_book` source. The default page size is 30 contacts and the maximum is 200, with `next_cursor` passed back as `page_token` for the next page.

The grant must come from the iCloud connector. A generic hosted IMAP grant for an iCloud address doesn't provide CardDAV contacts, even though it reads the same mailbox. The request below is the same one you'd send for any other provider:

```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",
      "metadata": {
        "key1": "customer-123",
        "crm_record": "crm-456"
      },
      "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 [appleContactsApi-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 [appleContactsApi-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)

```

```ruby [appleContactsApi-Ruby SDK]

require 'nylas'	

nylas = Nylas::Client.new(api_key: "<NYLAS_API_KEY>")
contacts, _ = nylas.contacts.list(identifier: "<NYLAS_GRANT_ID>")

contacts.each {|contact|
  puts "Name: #{contact[:given_name]} #{contact[:surname]} | " \
      "Email: #{contact[:emails][0][:email]} | ID: #{contact[:id]}"
}

```

```java [appleContactsApi-Java SDK]

import com.nylas.NylasClient;
import com.nylas.models.*;

public class ReadAllContacts {
  public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError {
    NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
    ListResponse<Contact> contacts = nylas.contacts().list("<NYLAS_GRANT_ID>");

    for(Contact contact : contacts.getData()) {
      System.out.println(contact);
      System.out.println("\n");
    }
  }
}


```

```kt [appleContactsApi-Kotlin SDK]

import com.nylas.NylasClient

fun main(args: Array<String>) {
  val nylas: NylasClient = NylasClient(apiKey = "<NYLAS_API_KEY>")
  val contacts = nylas.contacts().list("<NYLAS_GRANT_ID>")

  for(contact in contacts.data){
    println(contact)
  }
}


```

## What can you do with iCloud contacts through Nylas?

A native iCloud grant supports list, get, create, update, and delete on contacts in the user's iCloud address book. You can list explicit contact groups, filter contacts by group, and change membership through the contact's `groups` field. Filters for `email`, `phone_number`, and `group` work, and each list response holds at most 200 contacts.

A few iCloud-specific limits apply:

- **You assign groups but can't manage them.** The API doesn't create, rename, or delete group resources. Group IDs start with `carddav_group_v1_`.
- **Some fields aren't writable.** You can't write `manager_name`, `office_location`, or profile pictures, and `profile_picture=true` isn't supported.
- **IDs can change.** A contact moved inside iCloud can get a new ID, so store the latest ID from every response.
- **Pagination is cursor-only.** Offset pagination isn't supported.
- **One source per request.** `source=domain` and compound values such as `source=address_book,inbox` return an invalid request error.

The [Contacts API guide](/docs/v3/email/contacts/) covers create, update, metadata, and the full error contract across providers.

## What's next

- [List iCloud contacts](/docs/cookbook/contacts/list-contacts-icloud/) for CRUD, filters, IDs, and notification examples
- [Contacts API guide](/docs/v3/email/contacts/) for the shared contact model and source rules
- [Nylas Contacts API guide](/docs/cookbook/contacts/contacts-api-guide/) for how iCloud compares with Google, Microsoft, and Yahoo
- [iCloud provider guide](/docs/provider-guides/icloud/) for connector setup and app-specific passwords
- [Apple Calendar API](/docs/cookbook/calendar/apple-calendar-api/) for iCloud Calendar over CalDAV
- [iCloud Mail API](/docs/cookbook/email/icloud-mail-api/) for iCloud Mail over IMAP and SMTP