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?
Section titled “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 container type describes contacts stored on a CardDAV server, with iCloud as the example.
What’s the Apple Contacts framework?
Section titled “What’s the Apple Contacts framework?”The Contacts framework 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?
Section titled “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, 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. 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
Section titled “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 covers delivery behavior in detail.
How do I list iCloud contacts as JSON?
Section titled “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:
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'{ "request_id": "1", "data": [ { "birthday": "1960-12-31", "company_name": "Nylas", "emails": [ { "type": "work", }, { "type": "home", } ], "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"}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();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)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]}"}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"); } }}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?
Section titled “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, andprofile_picture=trueisn’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=domainand compound values such assource=address_book,inboxreturn an invalid request error.
The Contacts API guide covers create, update, metadata, and the full error contract across providers.
What’s next
Section titled “What’s next”- List iCloud contacts for CRUD, filters, IDs, and notification examples
- Contacts API guide for the shared contact model and source rules
- Nylas Contacts API guide for how iCloud compares with Google, Microsoft, and Yahoo
- iCloud provider guide for connector setup and app-specific passwords
- Apple Calendar API for iCloud Calendar over CalDAV
- iCloud Mail API for iCloud Mail over IMAP and SMTP