Files.fm API

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

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.