Contact Sync (CardDAV)

The built-in CardDAV server allows you to synchronize your contacts with your mobile device or computer (e.g. Apple Contacts in iPhone macOS Contacts or on Android with a third-party CardDAV client like DAVx⁵). This page is the served direction — your instance’s contacts going out to your devices. The subscription direction — pulling contacts in from someone else’s CardDAV server — is the “CardDAV contact sync” integration; who owns it when it breaks and how to diagnose it is in Integrations: ownership, diagnostics, and what breaks.

Enable CardDAV by setting the CARDDAV_ENABLED environment variable to true. Once enabled, the CardDAV server runs alongside the web interface. A standard discovery endpoint is available at /.well-known/carddav for automatic configuration.

Connecting Your Phone

iOS

  1. Open Settings > Contacts > Accounts > Add Account > Other.
  2. Select Add CardDAV Account.
  3. Enter the following:
    • Server: Your Mycorrhizal CRM URL (e.g., mycorrhizal.example.com)
    • User Name: Your Mycorrhizal CRM username or email
    • Password: Your Mycorrhizal CRM password
  4. Tap Next. iOS will automatically discover the CardDAV endpoint.
  5. Your contacts will begin syncing.

Android

Android does not include a native CardDAV client. You will need a third-party app such as DAVx5 (open source):

  1. Install DAVx5 from F-Droid or Google Play.
  2. Open DAVx5 and add a new account.
  3. Select Login with URL and user name.
  4. Enter:
    • Base URL: Your Mycorrhizal CRM URL followed by /carddav/ (e.g., https://mycorrhizal.example.com/carddav/)
    • User name: Your Mycorrhizal CRM username or email
    • Password: An app password created in Settings → API tokens (see below)
  5. DAVx5 will detect the address book. Select it and sync.
  6. Your Mycorrhizal CRM contacts will appear in your phone’s Contacts app.

Two-factor accounts need an app password. If TOTP or a passkey is enrolled on your account, the account password is refused on CardDAV and CalDAV (the server answers 403 with X-Mycorrhizal-Auth-Error: account-password-not-allowed-for-dav). Create an app password instead:

  1. In Mycorrhizal CRM, go to Settings → API tokens and create a token.
  2. Choose the contacts preset (CardDAV only), or full if the same token is also used for other things.
  3. Optionally choose no expiry for a DAV-only token. The default is 90 days.
  4. Copy the token when it is shown (it is displayed only once) and paste it into DAVx5’s Password field. If the account already exists in DAVx5, edit it there and replace the password.

Sync Behavior

  • Two-way sync: Changes made in Mycorrhizal CRM appear on your phone, and changes made on your phone are synced back to Mycorrhizal CRM. This also applies to profile pictures.
  • Conflict detection: Mycorrhizal CRM uses ETags to detect conflicts. If a contact has been modified on both the server and the client since the last sync, the client will be notified and can resolve the conflict.
  • Supported fields: Mycorrhizal CRM syncs all fields though now all fields might be visible in your client. In case you add additional fields on your client (like a secondary address) the fields will be preserved in the Mycorrhizal database but will not show in the Mycorrhizal CRM frontend.
  • CRM-only fields survive a vCard overwrite: circles, how-we-met, work information, contact information, the entity kind, and entry periods have no vCard property. A CardDAV PUT replaces the whole resource, so when the incoming card does not carry those fields the server keeps the stored values instead of clearing them; the same rule applies to contacts pulled in by a CardDAV subscription. Edit or clear them in the Mycorrhizal UI. Ordinary vCard fields remain fully client-controlled.
  • Sync only selected circles: Settings → CardDAV sync scope can keep the address book at “all contacts” (the default), sync only chosen circles, or sync everything except chosen circles. There is still one address book and one URL. The filter applies to listings, multiget, REPORT and resource GET, so an out-of-scope contact is not served at all. Sensitivity filtering is independent: a private/secret address is still withheld from an in-scope contact.
  • The first sync after narrowing deletes those contacts on the device. DAVx5 and Apple Contacts compare the server listing with their local copy and remove anything the server no longer lists. This is client-side only: Mycorrhizal deletes no contact and keeps the full record. Widening the scope downloads the contacts again on the next sync.
  • Out-of-scope edits are refused. A CardDAV PUT to an existing contact outside the scope returns 403 and writes nothing (a stale edit loses to the scope, but the server copy is intact). A DELETE of an out-of-scope contact returns 404, so a phone cleaning up a stale local copy does not loop. A newly created contact in “only selected circles” is filed into the configured default circle; if that default is missing, the create is refused rather than saved invisibly.
  • Subscribed contacts have no circle. A contact pulled in by an inbound CardDAV subscription is an ordinary contact with no circle, so it is not served under “only selected circles”; it is served under “all” and “all except selected circles” unless one of its circles is excluded.

Troubleshooting

  • Contacts not syncing: Verify that CARDDAV_ENABLED=true is set in your server environment and restart the application.
  • Discovery not working: Some clients require the full CardDAV URL instead of relying on auto-discovery. Try entering https://your-server.com/carddav/ directly as the server URL.
  • Locked out: After multiple failed login attempts, your account may be temporarily locked. Wait a few minutes and try again, or reset your password via the web interface.

Interoperability limitations

This is the operator-facing statement of what the CardDAV/CalDAV interoperability claim does and does not cover. The engineering evidence — the per-client manual matrix, the divergence registers for each reference server, and the reference implementations the automated legs run against — lives in the development docs (reference-client-matrix.md and testing.md).

  • One address book by design. The server exposes a single address book per user. A client that tries to create a second one (MKCOL) is refused with 403 — the interop-correct “single address book” answer, not a server error.
  • A new contact past PER_USER_CONTACT_LIMIT is refused with 507 Insufficient Storage. The quota is opt-in (0, the default, means unlimited) and applies to a CardDAV PUT that creates a contact as well as to the REST create; updating an existing contact at the limit still succeeds. 507 is the same status the REST create already returns for this refusal and RFC 4918’s “server is unable to store the representation” answer, so the client learns this is a capacity limit rather than bad credentials or a missing permission. A refused create writes nothing and fires no contact.created webhook. DAVx5 classifies every non-auth HTTP error, including this one, as a hard sync error (SyncExceptionHandler) and reports it instead of retrying it as transient; the exact Apple Contacts presentation is not automated (see reference-client-matrix.md), so it is not asserted here.
  • No CardDAV sync-token. go-webdav does not implement it, so clients fall back to a PROPFIND of depth 1 and compare locally. Incremental sync therefore costs a full listing of the collection.
  • CalDAV is read-only. A client subscribes to a calendar of activities and life events; it never writes back.
  • address-data version negotiation is not supported on the client side. Our outbound CardDAV client (used by remote address-book subscriptions) cannot request a vCard version, so against SabreDAV-based servers (Baikal, Nextcloud) a full refetch receives vCard 3.0 re-serializations. The per-server divergence registers in testing.md pin exactly what each server does.
  • Address coordinates are served as a standalone GEO property, not RFC 9554’s ADR;GEO=. The file exports (vCard 3.0/4.0, JSContact) attach a coordinate to its address the RFC 9554 way, but over CardDAV the coordinate is instead carried as a standalone GEO property (RFC 6350, value type uri) linked to its address by the shared PROP-ID — the pinned go-webdav re-encodes the card it is handed, and go-vcard cannot encode a parameter value containing the : and , of a geo: URI. A client that only reads the ADR;GEO= parameter form will not see the coordinate (the address and every other field are unaffected); clients that preserve the standalone property round-trip it losslessly, and our own GET→PUT handler reapplies it to the neutral address.
  • Automated client coverage is provision + pull. The automated DAVx5 leg covers account setup and pulling the canonical pathological fixture; push, incremental re-sync, and an explicit version-negotiation assertion are not yet automated. Apple Contacts (macOS/iOS) and Thunderbird are out of scope for the automated claim — no CI-drivable harness exists for either. See reference-client-matrix.md.
  • Field-level fidelity is governed by the DATA-01 matrix. Which canonical fields survive a round trip through each format — and which are unsupported or lossy — is stated per field in the field compatibility matrix and surfaced to the user as export-loss reports (DATA-02).

Back to top

Mycorrhizal CRM - Self-hosted personal contact management

This site uses Just the Docs, a documentation theme for Jekyll.