Table of Contents

API : CardDAV : WebDAV methods

Introduction

The CardDAV server answers the WebDAV methods below on https://api.telecomx.dk/carddav/…. Express has no native routing for PROPFIND and REPORT, so the whole tree is handled by one catch-all route that dispatches on the method. Anything not listed here is answered with 405 Method Not Allowed.

The body of PROPFIND, REPORT, PUT and POST is read as raw text (XML or vCard) with a limit of 10 MB - a larger body is cut off with 413 and the connection is closed. The limit has to be that high because a multiget of a whole address book, or a vCard with an embedded photo, is large.

OPTIONS

Advertises the DAV capabilities of the server.

DAV: 1, 2, 3, addressbook
Allow: OPTIONS, GET, PUT, DELETE, PROPFIND, REPORT, POST

Note: the API servers global OPTIONS handler (CORS preflight and load balancer health checks) answers before the CardDAV router is reached, so in practice an OPTIONS request returns {“options”: true} with the CORS headers and not the two headers above. Clients discover the server with PROPFIND instead, which is what iOS and DAVx5 do, so this has no practical effect today.

PROPFIND

Discovers collections and reads properties. Depth: 0 returns the resource itself, Depth: 1 also its children. The response is always 207 Multi-Status XML.

Path Returns
/carddav/ current-user-principal, resourcetype, displayname.
/carddav/principals/{employeeId}/ current-user-principal, addressbook-home-set, resourcetype (collection + principal), displayname (the employees name).
/carddav/addressbooks/{employeeId}/ The home set, and with Depth: 1 the three address books.
/carddav/addressbooks/{employeeId}/{book}/ resourcetype (collection + addressbook), displayname, CS:getctag, CS:push-transports, CS:pushkey, and with Depth: 1 one entry per contact with getetag and getcontenttype - plus the full vCard in address-data if it was asked for.
/carddav/addressbooks/{employeeId}/{book}/{id}.vcf getetag and getcontenttype.

Any other path, including one with another employees id, answers 404.

Request example

PROPFIND /carddav/addressbooks/1234567890ABCDEF12345678/personal/ HTTP/1.1
Depth: 1
Content-Type: application/xml; charset=utf-8
 
<D:propfind xmlns:D="DAV:">
  <D:prop>
    <D:getetag/>
    <D:getcontenttype/>
  </D:prop>
</D:propfind>

REPORT

Runs a CardDAV query against an address book collection. Two report types are supported:

Report Description
addressbook-multiget The body lists the hrefs to fetch. Each one is answered with its properties, or 404 Not Found inside the multi-status if it does not exist in that book. The contacts are loaded in a single database query, so a multiget of the whole book is cheap.
addressbook-query Returns every contact in the book. Filters in the request body are not evaluated - the full book is always returned.

sync-collection is not implemented. Any other report type answers 400 Unsupported REPORT type.

The response is 207 Multi-Status with getetag, getcontenttype and, when address-data was requested, the full vCard for each contact.

GET

Returns a single contact.

URL https://api.telecomx.dk/carddav/addressbooks/{employeeId}/{book}/{id}.vcf
Method GET
Response 200 with Content-Type: text/vcard; charset=utf-8 and an ETag header

The contact has to be in the book it is requested from: a personal contact fetched through /shared/ - or the other way round - answers 404. A contact belonging to another customer answers 403 access_denied.

PUT

Creates or updates a contact in the personal book. /shared/ and /colleagues/ are read-only and answer 403.

URL https://api.telecomx.dk/carddav/addressbooks/{employeeId}/personal/{id}.vcf
Method PUT
Body text/vcard - see vCard mapping
Header If-Match with the ETag the client last saw. Optional, but this is how a lost update is caught.
Response
201 Created New contact. Location and ETag headers are returned. A PUT to an id that does not exist creates the contact, which is how iOS adds one.
204 No Content Existing contact updated. ETag header is returned.
403 The book is read-only, or the contact exists but belongs to somebody else.
412 If-Match did not match the current ETag - the contact was changed by somebody else in the meantime.

An embedded or referenced PHOTO is uploaded to the image server; if the upload fails the rest of the contact is still saved.

DELETE

Deletes a contact from the personal book. /shared/ and /colleagues/ answer 403.

URL https://api.telecomx.dk/carddav/addressbooks/{employeeId}/personal/{id}.vcf
Method DELETE
Response 204 No Content

The contact is soft-deleted in the phonebook, exactly as a delete through the REST API.

POST

Subscribes or unsubscribes an Apple device to push notifications for a collection - see Push notifications. POST /carddav/notify is a different thing entirely, see Notify.

Status codes

Code Meaning
200 GET of a vCard, or unsubscribe accepted.
201 Contact created, or push subscription created.
204 Contact updated or deleted.
207 Multi-status, the normal answer to PROPFIND and REPORT.
400 Unsupported report type, or a subscription without a device token.
401 Missing or wrong credentials, see Authentication.
403 Read-only address book, or a resource belonging to another customer.
404 Unknown path, unknown contact, or a contact fetched from the wrong book.
405 Method not supported.
412 If-Match precondition failed.
413 Request body larger than 10 MB.
429 Rate limit for the token exceeded.