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