# Sync iCloud Contacts with CardDAV

Canonical: https://figment.so/blog/icloud-contacts-carddav

iCloud Contacts speaks CardDAV, a standard protocol ([RFC 6352](https://www.rfc-editor.org/rfc/rfc6352)) layered on WebDAV and HTTP. The discovery entry point is `contacts.icloud.com` on port 443 (SSL required). You authenticate with your Apple ID email and an app-specific password, discover your address book home in two PROPFIND requests, choose the specific collection to sync, then read and write individual contacts as vCard files. The sections below walk through each step and explain how ETag-based conflict detection keeps concurrent updates safe.

## What you need before connecting

**Apple ID with two-factor authentication.** App-specific passwords cannot be generated without 2FA enabled on your Apple Account.

**An app-specific password.** Go to [account.apple.com](https://account.apple.com), open **Sign-In and Security**, and select **App-Specific Passwords** ([support.apple.com/en-us/102654](https://support.apple.com/en-us/102654)). Generate one and save it — Apple shows it only once. Use your full Apple ID email as the CardDAV username.

**Alternative for supported apps.** Apple documents a third-party app authorization path for clients that access iCloud Mail, Calendar, and Contacts ([support.apple.com/en-us/121539](https://support.apple.com/en-us/121539)). When a client supports this flow, users enter their Apple Account email address and click **Allow** in Apple's prompt rather than generating an app-specific password manually. If your client does not support it, the app-specific password path works for CardDAV tools.

**SSL/TLS.** Port 443 and a valid certificate chain are required. Never skip SSL certificate verification.

## Discover your address book

CardDAV clients must not hardcode a contact list URL. iCloud routes accounts to partition hosts, and that destination can change. Use the two-stage discovery defined in [RFC 6352](https://www.rfc-editor.org/rfc/rfc6352) and [RFC 6764](https://www.rfc-editor.org/rfc/rfc6764).

**Stage 1 — well-known redirect.** Send an authenticated PROPFIND to the well-known URI:

```
PROPFIND https://contacts.icloud.com/.well-known/carddav
```

iCloud returns a redirect to your user principal URL on a partition host. Follow it with your credentials.

**Stage 2 — find the address book home set.** PROPFIND the principal URL for `CARDDAV:addressbook-home-set`:

```xml
PROPFIND {principal-url}
Depth: 0
Content-Type: application/xml

<D:propfind xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
  <D:prop>
    <C:addressbook-home-set />
  </D:prop>
</D:propfind>
```

The response contains a URL identifying the home collection. PROPFIND that URL with `Depth: 1` to list the individual address book collections and their `DAV:displayname` values.

**Stage 3 — select the right address book.** An account can contain multiple address book collections — the default address book, collections created by other apps, and so on. Read the `DAV:displayname` of each collection and choose the specific one you intend to sync. Never assume the first result is the only collection, and never blindly overwrite all collections without first inspecting their contents. Cache the principal URL per session; re-run discovery after a 301 or 404 on a previously valid path.

RFC 6352 also recommends requesting `DAV:current-user-principal` (defined in [RFC 5397](https://www.rfc-editor.org/rfc/rfc5397)) in the same first PROPFIND. Many clients batch both properties in one request to save a round trip.

## Read and write vCards

**Fetch a single contact.** GET the resource URL returned by the collection PROPFIND. The server responds with a `text/vcard` body. iCloud supports vCard 3.0 ([RFC 2426](https://www.rfc-editor.org/rfc/rfc2426)) at minimum; RFC 6352 says servers should also support vCard 4.0 ([RFC 6350](https://www.rfc-editor.org/rfc/rfc6350)), so clients that handle both formats are more robust against future responses.

**List contacts efficiently.** A REPORT with the `addressbook-query` report type lets you retrieve multiple vCards — or just their ETags — in one response:

```xml
REPORT {addressbook-url}
Depth: 1
Content-Type: application/xml

<C:addressbook-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
  <D:prop>
    <D:getetag />
    <C:address-data />
  </D:prop>
</C:addressbook-query>
```

The server returns `207 Multi-Status` with one `<D:response>` per contact. On first sync, request only `getetag` to build your local inventory cheaply, then fetch `address-data` only for resources whose ETag has changed since the last sync.

**Create or update a contact.** PUT a vCard body to `{addressbook-url}/{uid}.vcf`. RFC 6352 requires each vCard to carry a unique `UID` property within the collection:

```
PUT {addressbook-url}/{uid}.vcf
Content-Type: text/vcard

BEGIN:VCARD
VERSION:3.0
UID:{uid}
FN:Alex Chen
N:Chen;Alex;;;
EMAIL;type=INTERNET:alex@example.com
END:VCARD
```

A `201 Created` response confirms the resource was saved and includes an `ETag` header — store it.

## Handle conflicts with ETags

Every contact resource carries an `ETag` that changes whenever the resource is modified. Use it to guard against overwriting concurrent changes.

**Conditional update.** Include `If-Match` with the stored ETag when you PUT an update:

```
PUT {addressbook-url}/{uid}.vcf
If-Match: "abc123etag"
Content-Type: text/vcard
...
```

If the resource changed since you read it, the server returns `412 Precondition Failed`. Fetch the current version with GET, merge your changes, and retry with the new ETag.

**Safe creation.** Add `If-None-Match: *` when creating a new resource to prevent overwriting an existing contact that shares the same URL.

**Incremental sync.** If the server advertises `DAV:sync-collection` REPORT support (via `DAV:supported-report-set`) as defined in [RFC 6578](https://www.rfc-editor.org/rfc/rfc6578), use sync tokens to retrieve only changed and deleted resources since your last sync, rather than comparing ETags across the full collection.

## Common blockers

| Symptom | Likely cause | Fix |
|---|---|---|
| `401 Unauthorized` immediately | Apple Account password used instead of app-specific password | Generate an app-specific password at account.apple.com |
| `401` with correct credentials | 2FA not enabled | Enable 2FA before generating app-specific passwords |
| Client stuck on `contacts.icloud.com` | Redirect not followed with credentials | Configure the client to resend credentials on redirects |
| `404` on a previously valid principal URL | Partition routing changed | Re-run well-known discovery to get the current URL |
| `412 Precondition Failed` on PUT | Stored ETag is stale | GET the latest version, merge your changes, PUT with the new ETag |

## Contacts sandbox in Figment Apple Connect

Figment Apple Connect provides a synthetic read-only sandbox for Contacts. The sandbox endpoint returns invented sample records — useful for building and testing your sync integration before connecting to live CardDAV data.

```
GET https://figment.so/api/apple-connect/v1/connections/{connection_id}/contacts
Authorization: Bearer {ACCESS_TOKEN}
```

Each item in the response includes `id`, `name`, `organization`, `emails`, and `phones`. The response envelope includes `mode: "sandbox"` and `scope: "synthetic sandbox data"`. This is not live iCloud Contacts data; live customer access to Contacts through Figment is not yet available. For live contact sync, the CardDAV protocol described in this article is the Apple-supported path.

See [Connect iCloud Mail, Calendar, and Contacts](/icloud-connect) for the multi-protocol overview, [Read and Update iCloud Calendars with CalDAV](/blog/icloud-calendar-caldav) for the calendar equivalent, [Connect iCloud Mail with IMAP and SMTP](/blog/icloud-mail-imap-smtp) for the email protocol, and [OAuth for iCloud: Connect Apple Data to Your App](/oauth-for-icloud) for the full Figment grant model.

**[Connect Apple Account](/apple-connect/developers)** to get sandbox access and explore Contacts data with synthetic records.
