iCloud exposes its address book through CardDAV instead of a public REST API. A native iCloud grant lets you use the Nylas Contacts API to read and write that CardDAV data through the same JSON schema you use for Google, Microsoft, Yahoo, and Exchange contacts.
Why use Nylas instead of iCloud CardDAV directly?
Section titled “Why use Nylas instead of iCloud CardDAV directly?”- One REST contract. Nylas handles CardDAV discovery,
PROPFINDandREPORTrequests,vCardparsing, conditional writes, and pagination. - Grant-bound resource validation. Nylas treats CardDAV
hrefvalues as untrusted and validates them against the address books discovered for the grant. - Normalized groups and fields. Explicit group
vCardrecords become Contact Groups, while address-book containers and combined views such as All Contacts stay out of the group list. - Cross-provider application code. The same Contacts endpoints and response schema work across supported providers.
Before you begin
Section titled “Before you begin”You need:
- A Nylas application with a valid API key.
- An iCloud connector in the application.
- A native iCloud grant authenticated with an app-specific password.
Authenticate an iCloud account
Section titled “Authenticate an iCloud account”iCloud Contacts doesn’t use OAuth scopes. Ask the user to create an app-specific password by following Apple’s app-specific password instructions, then use it to authenticate the native iCloud grant. See the iCloud provider guide for connector and authentication details.
List iCloud CardDAV contacts
Section titled “List iCloud CardDAV contacts”Call GET /v3/grants/{grant_id}/contacts. The default and supported writable source is address_book, so you can omit the source parameter or set source=address_book explicitly.
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)The native iCloud source rules are strict:
- Omitting
sourceselects CardDAVaddress_bookcontacts. source=inboxis available only when Nylas enables the legacy mailbox-derived feature. It’s read-only.source=domainand compound values such assource=address_book,inboxreturn an invalid request error.- Offset pagination is unsupported. Use the returned
next_cursoraspage_token.
Create, update, and delete iCloud contacts
Section titled “Create, update, and delete iCloud contacts”The standard Contact create, update, and delete endpoints write the native iCloud address book through CardDAV. The API selects the grant’s writable address book. If it can’t find one, or discovers more than one possible writable address book, the write fails instead of guessing.
curl --compressed --request POST \ --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' \ --data '{ "birthday": "1960-12-31", "company_name": "Nylas", "emails": [ { "email": "[email protected]", "type": "work" }, { "email": "[email protected]", "type": "home" } ], "given_name": "Leyah", "groups": [ { "id": "starred" }, { "id": "friends" } ], "im_addresses": [ { "type": "jabber", "im_address": "leyah_jabber" }, { "type": "msn", "im_address": "leyah_msn" } ], "job_title": "Software Engineer", "manager_name": "Bill", "middle_name": "Allison", "metadata": { "key1": "customer-123", "crm_record": "crm-456" }, "nickname": "Allie", "notes": "Loves Ramen", "office_location": "123 Main Street", "phone_numbers": [ { "number": "+1-555-555-5555", "type": "work" }, { "number": "+1-555-555-5556", "type": "home" } ], "physical_addresses": [ { "type": "work", "street_address": "123 Main Street", "postal_code": "94107", "state": "CA", "country": "USA", "city": "San Francisco" }, { "type": "home", "street_address": "456 Main Street", "postal_code": "94107", "state": "CA", "country": "USA", "city": "San Francisco" } ], "source": "address_book", "surname": "Miller", "web_pages": [ { "type": "work", "url": "<WEBPAGE_URL>" }, { "type": "home", "url": "<WEBPAGE_URL>" } ] }'curl --compressed --request PUT \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/contacts/<CONTACT_ID>' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json' \ --data '{ "birthday": "1960-12-31", "company_name": "Nylas", "emails": [ { "email": "[email protected]", "type": "work" }, { "email": "[email protected]", "type": "home" } ], "given_name": "Leyah", "groups": [ { "id": "starred" }, { "id": "all" } ], "im_addresses": [ { "type": "jabber", "im_address": "leyah_jabber" }, { "type": "msn", "im_address": "leyah_msn" } ], "job_title": "Software Engineer", "manager_name": "Bill", "middle_name": "Allison", "metadata": { "key1": "customer-123", "crm_record": "crm-789" }, "nickname": "Allie", "notes": "Loves Ramen", "office_location": "123 Main Street", "phone_numbers": [ { "number": "+1-555-555-5555", "type": "work" }, { "number": "+1-555-555-5556", "type": "home" } ], "physical_addresses": [ { "type": "work", "street_address": "123 Main Street", "postal_code": "94107", "state": "CA", "country": "USA", "city": "San Francisco" }, { "type": "home", "street_address": "456 Main Street", "postal_code": "94107", "state": "CA", "country": "USA", "city": "San Francisco" } ], "source": "address_book", "surname": "Miller", "web_pages": [ { "type": "work", "url": "<WEBPAGE_URL>" }, { "type": "home", "url": "<WEBPAGE_URL>" } ] }'curl --compressed --request DELETE \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/contacts/<CONTACT_ID>' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json'CardDAV updates use ETag values internally for conditional writes, but the ETag isn’t part of the public Contact ID. A conflicting provider edit can produce a conflict or partial-failure error. Fetch the current contact before retrying.
Filter and paginate iCloud contacts
Section titled “Filter and paginate iCloud contacts”iCloud supports the email, phone_number, and group filters for CardDAV address-book contacts. Use one source per request.
curl --request GET \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/contacts?source=address_book&email=jane&limit=50' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'Continue through the result set by passing next_cursor back as page_token:
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>'Contact metadata filtering uses metadata_pair=key1:value. You can combine it with limit and page_token, but not with email, phone_number, group, recurse, or a non-default source. See Add metadata to a contact for create, update, clear, and cleanup behavior.
iCloud contact and group IDs
Section titled “iCloud contact and group IDs”Native contact IDs start with carddav_v1_; Contact Group IDs start with carddav_group_v1_. Both encode only the canonical provider href and apply to one grant. Treat them as opaque values and persist the latest ID from every response.
An ordinary edit keeps the same contact ID while the href remains stable. If the user moves a contact in iCloud, the href and ID can change. Nylas can then observe a deletion under the old ID and an update under the new ID. The API doesn’t keep an old-to-new mapping, and href reuse means the ID is a current locator rather than a permanent tombstone.
GET /contacts/groups returns only explicit group vCard records. All Contacts and address-book collections aren’t mutable groups. Filter members with GET /contacts?group=<CONTACT_GROUP_ID>, and change membership through the Contact groups field. The API doesn’t create, rename, or delete Contact Group resources.
iCloud contact notifications
Section titled “iCloud contact notifications”Native iCloud grants can emit contact.updated and contact.deleted for provider-originated CardDAV changes and Contacts API writes.
The first successful CardDAV scan establishes a baseline and doesn’t publish the user’s existing contacts. Later changes appear after the next configured polling cycle plus processing and retry time, so delivery isn’t real-time. Duplicate notifications are possible; make the handler idempotent and deduplicate by the CloudEvent id.
iCloud field and error behavior
Section titled “iCloud field and error behavior”- Profile-picture retrieval with
profile_picture=trueis unsupported for native CardDAV contacts. - Nylas validates address-book and group
hrefvalues against resources discovered for the grant. Invalid or cross-grant IDs fail closed. - Provider authentication, permission, resource-size, rate-limit, and timeout errors are returned as provider errors.
- Concurrent group membership changes can conflict. Fetch the latest Contact and Contact Groups before retrying.
- Contact metadata is Nylas-owned and never written to the iCloud
vCard. Provider and metadata writes are separate and not atomic.
List iCloud contacts from the terminal
Section titled “List iCloud contacts from the terminal”After the native grant exists, nylas contacts list --source address_book returns its CardDAV contacts without a separate CardDAV client.
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:
# List 50 contacts (default)nylas contacts list
# Only the saved address book, skipping auto-collected sendersnylas contacts list --source address_book --limit 100Every 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:
# Find contacts at a company who have an email on filenylas contacts search --company "Acme" --has-emailSee the contacts list and contacts search command reference for every flag.
What’s next
Section titled “What’s next”- Manage contacts with the Contacts API for the shared CRUD, metadata, ID, and error contract
- Work with Contact Groups for group listing, filtering, and membership examples
- How to search contacts for provider and metadata filters
- iCloud provider guide for connector and app-specific password setup
- Contact notification schemas for payload examples