Files.fm CalDAV and CardDAV API
Files.fm provides CalDAV for calendars and CardDAV for address books through its SabreDAV server. Both support programmatic access over HTTPS and compatible DAV clients. Calendar events use iCalendar (.ics); contacts use vCard (.vcf). Discovery and queries use DAV XML.
Server and authentication
| Setting | Value |
|---|---|
| Base URL for both protocols | https://calendar.failiem.lv/ |
| Authentication | HTTP Basic over HTTPS with your Files.fm username and password |
| Calendar home, typical path | /calendars/{username}/ |
| Address-book home, typical path | /addressbooks/{username}/ |
| Principal, typical path | /principals/{username}/ |
Use your canonical Files.fm username consistently. Discover resource URLs as described below rather than constructing them from email addresses or display names. Existing calendars associated with an email-based principal may require support to link them to the username used by the web workspace.
The DAV authenticator also accepts a valid Files.fm User Access Token in the Basic password field. This is not an Authorization: Bearer interface: do not assume that REST API keys, folder access keys, or a calendar's public sharing hash are DAV credentials. Account/IP restrictions and password policies apply. Password-based connections requiring two-factor authentication may need the emailed IP confirmation before access succeeds; an unconfirmed attempt can return 403.
The examples use curl --user 'YOUR_USERNAME', which prompts for the password. Replace placeholders with your own account and discovered URLs. Send credentials only to the intended HTTPS server. The PUT and DELETE examples change data in the authenticated account.
Discover calendars and address books
1. Find the authenticated principal
curl --user 'YOUR_USERNAME' -i -X PROPFIND \
'https://calendar.failiem.lv/' \
-H 'Depth: 0' -H 'Content-Type: application/xml; charset=utf-8' \
--data-binary @- <<'XML'
<d:propfind xmlns:d="DAV:">
<d:prop><d:current-user-principal/></d:prop>
</d:propfind>
XML
A successful DAV property response normally uses 207 Multi-Status. Read the d:href inside d:current-user-principal, for example /principals/YOUR_USERNAME/. XML prefixes can differ; match namespace URIs and element names. Check each d:propstat/d:status: a 207 response can contain missing or forbidden properties.
2. Find both home collections
Send this request to the principal URL returned above:
curl --user 'YOUR_USERNAME' -i -X PROPFIND \
'https://calendar.failiem.lv/principals/YOUR_USERNAME/' \
-H 'Depth: 0' -H 'Content-Type: application/xml; charset=utf-8' \
--data-binary @- <<'XML'
<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav"
xmlns:a="urn:ietf:params:xml:ns:carddav">
<d:prop><c:calendar-home-set/><a:addressbook-home-set/></d:prop>
</d:propfind>
XML
Resolve returned href values against the server URL. Use explicit discovery from this base URL; do not depend on .well-known redirects or enumeration of other users.
3. List the collections in each home
Send PROPFIND with Depth: 1 to each discovered home, using this XML body:
<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
<d:prop>
<d:displayname/><d:resourcetype/><d:current-user-privilege-set/>
<d:supported-report-set/><d:sync-token/>
<c:supported-calendar-component-set/>
</d:prop>
</d:propfind>
Select responses whose resourcetype contains calendar in the CalDAV namespace or addressbook in the CardDAV namespace. Calendar-only properties may be absent on address books. Keep the collection href; its display name is not its URL. Calendar homes may also contain scheduling resources, which are not ordinary calendars.
The backend can automatically create a default calendar or address book when listing a user's collections for the first time. Discovery is therefore not guaranteed to leave an unused account without collections. Do not hard-code default as the only collection.
CalDAV: read and write events
Query a date range
Replace CALENDAR_URI with the discovered collection path segment. Dates below are UTC and select September 2026; choose the range your integration needs.
curl --user 'YOUR_USERNAME' -i -X REPORT \
'https://calendar.failiem.lv/calendars/YOUR_USERNAME/CALENDAR_URI/' \
-H 'Depth: 1' -H 'Content-Type: application/xml; charset=utf-8' \
--data-binary @- <<'XML'
<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="20260901T000000Z" end="20261001T000000Z"/>
</c:comp-filter></c:comp-filter></c:filter>
</c:calendar-query>
XML
Each matching response supplies an object href, getetag, and calendar-data. A recurring series can be returned as one iCalendar object with recurrence rules and exceptions, rather than one object per occurrence. Retain the complete object when editing it, including properties your integration does not understand. Check the collection's advertised component types before storing components other than VEVENT.
Create an event
Save this as event.ics, using iCalendar CRLF line endings. Generate a unique UID and object filename for each new event; the example values are placeholders.
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Integration//Calendar//EN
BEGIN:VEVENT
UID:integration-example-20260915@example.invalid
DTSTAMP:20260909T120000Z
DTSTART:20260915T090000Z
DTEND:20260915T093000Z
SUMMARY:Integration meeting
END:VEVENT
END:VCALENDAR
curl --user 'YOUR_USERNAME' -i -X PUT \
'https://calendar.failiem.lv/calendars/YOUR_USERNAME/CALENDAR_URI/integration-example.ics' \
-H 'Content-Type: text/calendar; charset=utf-8' \
-H 'If-None-Match: *' --data-binary @event.ics
Creation normally returns 201 Created. For updates and deletion, use the conditional requests described below.
CardDAV: read and write contacts
CardDAV stores address-book contacts. It is separate from DVS contact-person records; no automatic synchronization with the DVS API is implied. Calendar membership sharing does not grant address-book access.
Query contacts
Use the discovered address-book URL, replacing ADDRESSBOOK_URI:
curl --user 'YOUR_USERNAME' -i -X REPORT \
'https://calendar.failiem.lv/addressbooks/YOUR_USERNAME/ADDRESSBOOK_URI/' \
-H 'Depth: 1' -H 'Content-Type: application/xml; charset=utf-8' \
--data-binary @- <<'XML'
<a:addressbook-query xmlns:d="DAV:" xmlns:a="urn:ietf:params:xml:ns:carddav">
<d:prop><d:getetag/><a:address-data/></d:prop>
<a:filter>
<a:prop-filter name="FN">
<a:text-match collation="i;unicode-casemap" match-type="contains">Example</a:text-match>
</a:prop-filter>
</a:filter>
</a:addressbook-query>
XML
The response contains matching object URLs, ETags, and vCard data. To list all object URLs and ETags, use PROPFIND with Depth: 1 on the address book and request d:resourcetype and d:getetag; skip the collection itself. Retrieve individual contacts with GET, or batches with addressbook-multiget when advertised.
Create a contact
Save this as contact.vcf with vCard CRLF line endings and a unique UID:
BEGIN:VCARD
VERSION:3.0
UID:integration-contact-example@example.invalid
FN:Example Contact
N:Contact;Example;;;
EMAIL:contact@example.invalid
END:VCARD
curl --user 'YOUR_USERNAME' -i -X PUT \
'https://calendar.failiem.lv/addressbooks/YOUR_USERNAME/ADDRESSBOOK_URI/integration-example.vcf' \
-H 'Content-Type: text/vcard; charset=utf-8' \
-H 'If-None-Match: *' --data-binary @contact.vcf
Retrieve, update, and delete an object
These operations apply to both event and contact URLs. Use an actual object URL from discovery/query results, not a collection URL:
# Fetch content and the current ETag response header.
curl --user 'YOUR_USERNAME' -i 'https://calendar.failiem.lv/OBJECT_PATH'
# Replace an event only if its ETag still matches. Preserve the ETag's quotes.
curl --user 'YOUR_USERNAME' -i -X PUT \
'https://calendar.failiem.lv/OBJECT_PATH' \
-H 'If-Match: "ETAG_FROM_SERVER"' \
-H 'Content-Type: text/calendar; charset=utf-8' --data-binary @event.ics
# Delete this object only if its ETag still matches.
curl --user 'YOUR_USERNAME' -i -X DELETE \
'https://calendar.failiem.lv/OBJECT_PATH' \
-H 'If-Match: "ETAG_FROM_SERVER"'
For contact updates, use text/vcard; charset=utf-8 and @contact.vcf. Updates normally return 204 No Content; fetch the current representation/ETag after writing if needed. A failed ETag precondition returns 412: refetch and reconcile changes before retrying. Do not retry without the precondition and overwrite another user's changes. Treat the resource URL, UID, and ETag as distinct values.
Synchronization and collection management
- Discover
DAV:supported-report-setandDAV:sync-tokenon each collection. Where advertised, useREPORTwithDAV:sync-collection, the saved token,DAV:sync-levelof1, andDAV:getetag. An empty token requests the initial synchronization state. Save the returned token; subsequent reports identify changed objects and deleted object URLs (404statuses inside the multistatus). - If the sync token is rejected as invalid, perform a fresh synchronization. Fetch changed objects using
GETor the advertisedcalendar-multiget/addressbook-multigetreports. Without sync support, compare an ETag listing with your previous complete listing; a date-filtered event query alone cannot establish all deletions. - Calendar creation uses
MKCALENDAR; address-book creation uses extendedMKCOLwithDAV:resourcetypecontainingDAV:collectionand CardDAVaddressbook. UsePROPPATCHfor supported collection properties. Check permissions and advertised capabilities before collection operations. DELETEon an owned collection removes that calendar/address book and its objects. This differs from deleting one.icsor.vcfobject.
Shared calendars and the web workspace
Owners manage calendar membership in the Files.fm calendar workspace at https://files.fm/calendar/. Members connect to CalDAV with their own credentials. Their calendar home exposes a filesfm-shared-{calendarId} collection pointing to the owner's original events and synchronization history.
Members can read, create, edit, and delete events, including private events. They cannot manage membership or the owner's global calendar settings through this collection. Supported personal collection properties include display name, color, order, and description. Deleting the member's shared collection removes their membership; deleting an event inside it removes the event for everyone. Membership revocation takes effect on subsequent requests.
Public calendar links and /calendar/{hash}/calendar.ics subscriptions are separate read-only interfaces. A public link alone grants no CalDAV access. The web workspace uses the same calendar data, but some imported events and recurrence patterns can only be edited through CalDAV. The workspace's /calendar/api.php requires a web session and CSRF token for writes and is not a public REST API contract. CalDAV and CardDAV are not routes under api_v2/ and are not included in the REST OpenAPI specification.
Responses and troubleshooting
| HTTP status | Meaning / action |
|---|---|
200, 201, 204 |
Successful retrieval, creation, or update/deletion, depending on the method |
207 |
DAV XML multistatus; inspect each response/property status |
400 |
Malformed request or data; inspect the DAV error body |
401 |
Authentication required or credentials rejected |
403 |
Insufficient permission, account/IP policy, unconfirmed 2FA, or DAV validation failure; inspect the error body |
404 |
Resource missing or no longer accessible; rediscover where appropriate |
409 |
Parent collection/state conflict; check the destination and current state |
412 |
Conditional request failed; retrieve the latest object and reconcile |
503 |
Service unavailable; retry with backoff |
Use namespace-aware XML parsing. DAV error bodies are XML, not the REST API's JSON format. For account setup or integration support, contact support@files.fm.
Implementation and protocol references
Files.fm behavior was checked against webdav_db_v2/app/caldavapp.php, the custom authentication, calendar, and address-book backends, and new/calendar/README.md. This guide documents the implemented interface; it does not claim that every DAV extension or client-specific feature is supported. No production data was written to validate these examples.
For further protocol details, see the official SabreDAV guides for CalDAV clients and CardDAV clients.