# 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

```sh
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:

```sh
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:

```xml
<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.

```sh
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.

```ical
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
```

```sh
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`:

```sh
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:

```vcard
BEGIN:VCARD
VERSION:3.0
UID:integration-contact-example@example.invalid
FN:Example Contact
N:Contact;Example;;;
EMAIL:contact@example.invalid
END:VCARD
```

```sh
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:

```sh
# 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-set` and `DAV:sync-token` on each collection. Where advertised, use `REPORT` with `DAV:sync-collection`, the saved token, `DAV:sync-level` of `1`, and `DAV:getetag`. An empty token requests the initial synchronization state. Save the returned token; subsequent reports identify changed objects and deleted object URLs (`404` statuses inside the multistatus).
- If the sync token is rejected as invalid, perform a fresh synchronization. Fetch changed objects using `GET` or the advertised `calendar-multiget` / `addressbook-multiget` reports. 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 extended `MKCOL` with `DAV:resourcetype` containing `DAV:collection` and CardDAV `addressbook`. Use `PROPPATCH` for supported collection properties. Check permissions and advertised capabilities before collection operations.
- `DELETE` on an owned collection removes that calendar/address book and its objects. This differs from deleting one `.ics` or `.vcf` object.

## 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](https://sabre.io/dav/building-a-caldav-client/) and [CardDAV clients](https://sabre.io/dav/building-a-carddav-client/).
