Calendar & Contacts
EUnifyer supports both standards-based protocols (CalDAV, CardDAV) and a REST API for calendar and contacts access. For external integrations, the standards-based path is recommended — it works with any compatible client out of the box.
Which path to use
| Use case | Recommended |
|---|---|
| Connect Apple Calendar, Thunderbird, GNOME Calendar | CalDAV |
| Connect Apple Contacts, Thunderbird, GNOME Contacts | CardDAV |
| Build a server-side integration or sync job | CalDAV / CardDAV or REST |
| Mobile app syncing calendars/contacts | CalDAV / CardDAV |
| REST programmatic access (events, contacts) | REST API |
CalDAV (Calendar)
- Short client URL:
https://cal.eunifyer.com - Canonical endpoint:
https://cal.eunifyer.com/api/v1/caldav/ - Standard: RFC 4791 (CalDAV) + RFC 6638 (scheduling extensions)
- Auth: HTTP Basic (
full-email:app-password) orAuthorization: Bearer <token> - Service discovery:
https://cal.eunifyer.com/.well-known/caldav(RFC 6764)
Connect a calendar client
Give users this URL for calendar client setup:
https://cal.eunifyer.comMost clients (Apple Calendar, Thunderbird, GNOME Calendar) auto-configure from the well-known URL. The short URL handles root discovery and returns canonical resource paths. After discovery, the client reads user calendars via PROPFIND and syncs events via REPORT.
For Basic Auth, generate an App Password in Settings > App Passwords. The normal EUnifyer account password is not accepted.
Programmatic CalDAV access
Read events in a date range:
curl -X REPORT "https://cal.eunifyer.com/api/v1/caldav/{username}/{calendar-uid}/" \
-u "user@example.com:app-password" \
-H "Content-Type: application/xml; charset=utf-8" \
-H "Depth: 1" \
-d '<?xml version="1.0" encoding="utf-8"?>
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop><D:getetag/><C:calendar-data/></D:prop>
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:time-range start="20260401T000000Z" end="20260430T235959Z"/>
</C:comp-filter>
</C:comp-filter>
</C:filter>
</C:calendar-query>'Create or update an event:
curl -X PUT "https://cal.eunifyer.com/api/v1/caldav/{username}/{calendar-uid}/{event-uid}.ics" \
-u "user@example.com:app-password" \
-H "Content-Type: text/calendar; charset=utf-8" \
-d 'BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example//EN
BEGIN:VEVENT
UID:event-uid@example.com
DTSTART:20260420T100000Z
DTEND:20260420T110000Z
SUMMARY:Team standup
END:VEVENT
END:VCALENDAR'Delete an event:
curl -X DELETE "https://cal.eunifyer.com/api/v1/caldav/{username}/{calendar-uid}/{event-uid}.ics" \
-u "user@example.com:app-password"REST Calendar API
Base path: /api/v1/calendar/
Auth: Bearer token (PAT, Keycloak JWT)
Scopes: calendar:read (read), calendar:write (create/update/delete)
Endpoints
GET /api/v1/calendar/calendars → list of CalendarInfo
GET /api/v1/calendar/events?start=…&end=… → events across all calendars
GET /api/v1/calendar/calendars/{cal_id}/events → events in one calendar
GET /api/v1/calendar/calendars/{cal_id}/events/{uid} → single event
POST /api/v1/calendar/calendars/{cal_id}/events → create event
PUT /api/v1/calendar/calendars/{cal_id}/events/{uid} → replace event
DELETE /api/v1/calendar/calendars/{cal_id}/events/{uid} → delete event
POST /api/v1/calendar/freebusy → free/busy query
POST /api/v1/calendar/calendars/{cal_id}/import → import ICS file
GET /api/v1/calendar/calendars/{cal_id}/export.ics → export calendar as ICSExample — list upcoming events
curl "https://api.eunifyer.com/api/v1/calendar/events?start=2026-04-01T00:00:00Z&end=2026-04-30T23:59:59Z" \
-H "Authorization: Bearer $TOKEN"Example — create an event
curl -X POST https://api.eunifyer.com/api/v1/calendar/calendars/$CAL_ID/events \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Quarterly review",
"start": "2026-04-20T09:00:00Z",
"end": "2026-04-20T10:00:00Z",
"description": "Q1 results discussion",
"attendees": ["alice@example.com", "bob@example.com"]
}'Import and export
# Import ICS file
curl -X POST https://api.eunifyer.com/api/v1/calendar/calendars/$CAL_ID/import \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: text/calendar" \
--data-binary @events.ics
# Export as ICS
curl https://api.eunifyer.com/api/v1/calendar/calendars/$CAL_ID/export.ics \
-H "Authorization: Bearer $TOKEN" \
-o calendar.icsCardDAV (Contacts)
- Short client URL:
https://car.eunifyer.com(orhttps://contacts.eunifyer.com) - Canonical endpoint:
https://car.eunifyer.com/api/v1/carddav/ - Standard: RFC 6352 (CardDAV)
- Auth: HTTP Basic (
full-email:app-password) orAuthorization: Bearer <token> - Service discovery:
https://car.eunifyer.com/.well-known/carddav - ETag support: Active — use
If-Matchfor safe updates
Connect a contacts client
Give users this URL for contacts client setup:
https://car.eunifyer.comcar. pairs with cal. for CalDAV, so users setting up both only have to
remember one pattern. contacts.eunifyer.com is the older name for the same
endpoint and keeps working — already-configured clients need no change.
Clients (Apple Contacts, Thunderbird, GNOME Contacts) auto-configure from the well-known URL. The full endpoint remains available for clients that require an explicit path.
Programmatic CardDAV access
List contacts in an address book:
curl -X REPORT "https://contacts.eunifyer.com/api/v1/carddav/{username}/{book-uid}/" \
-u "user@example.com:app-password" \
-H "Content-Type: application/xml; charset=utf-8" \
-H "Depth: 1" \
-d '<?xml version="1.0" encoding="utf-8"?>
<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>'Create or update a contact (VCard):
curl -X PUT "https://contacts.eunifyer.com/api/v1/carddav/{username}/{book-uid}/{contact-uid}.vcf" \
-u "user@example.com:app-password" \
-H "Content-Type: text/vcard; charset=utf-8" \
-d 'BEGIN:VCARD
VERSION:3.0
UID:contact-uid@example.com
FN:Alice Chen
N:Chen;Alice;;;
EMAIL:alice@example.com
TEL:+1-555-0100
END:VCARD'REST Contacts API
Base path: /api/v1/contacts/
Auth: Bearer token
Scopes: contacts:read / contacts:write
Endpoints
GET /api/v1/contacts/books → list of AddressBook
GET /api/v1/contacts/books/{book_id} → list of ContactSummary
GET /api/v1/contacts/books/{book_id}/{contact_id} → ContactDetail
POST /api/v1/contacts/books/{book_id} → create contact
PUT /api/v1/contacts/books/{book_id}/{contact_id} → replace contact
DELETE /api/v1/contacts/books/{book_id}/{contact_id} → delete contact
GET /api/v1/contacts/search?q=… → search contacts
GET /api/v1/contacts/directory → organisation directoryExample — search contacts
curl "https://api.eunifyer.com/api/v1/contacts/search?q=alice" \
-H "Authorization: Bearer $TOKEN"Webhook events
| Event | Fires when |
|---|---|
calendar.event.created | A calendar event is created |
calendar.event.updated | A calendar event (or single occurrence) is updated |
Subscribe via the Webhooks API to receive real-time notifications.