Every request below /carddav/ - except /carddav/notify - is authenticated with HTTP Basic authentication (RFC 7617), because that is all a contacts app can do. There is no login endpoint and no session; the client sends the credentials on every single request.
| Realm | TelecomX CardDAV |
|---|---|
| User name | The employees e-mail address. |
| Password | The employees password, or one of the employees own persistent tokens used as an app specific password. |
A missing or unusable Authorization header answers 401 with WWW-Authenticate: Basic realm=“TelecomX CardDAV”, which is what makes the client show its credentials dialog.
<email>:<password> is looked up in Redis (key carddav-token:<md5-hash>). On a hit the cached Telecom X token is validated and the request continues without touching bcrypt. If the employee has since been switched to passkey-only, the cache entry is deleted and the request falls through to the steps below.persistentTokens list, and that token still validates, the request continues. This is the app specific password described below.Whichever path is taken, the resulting token is the one embedded in the photo URLs of the served vCards, so the device can download contact pictures from the image server.
An employee with webauthnOnly set has no usable password - a contacts app cannot perform a WebAuthn ceremony. The password step is therefore refused with 401 webauthn_only, and such an employee must instead use an app specific password:
DELETE /employee/{id}/token/{token} - the client is then refused at the next poll.
The token is only accepted when it is found in that employees own token list, so a token belonging to somebody else is useless even though it is a valid token. Turning webauthnOnly on for an employee that already syncs with a password also invalidates the cached credentials, so their client stops at the next poll and must be given a token.
| Restriction | Behaviour |
|---|---|
| IP restriction | Not enforced. Token validation is deliberately given no IP address on this transport, so an employees ipRestriction (country or CIDR allow list) does not apply to CardDAV - a phone has to keep syncing while it roams abroad or moves onto mobile data. The token is still created with the IP the client authenticated from. |
| Rate limiting | Every authenticated request is counted in the token scope, keyed by a SHA-256 hash of the token. CardDAV clients poll aggressively, so this is the limit that protects the API. Exceeding it answers 429. |
| Brute force | A failed authentication registers a brute force attempt for the client IP, exactly as a failed login does. Enough failures block the IP for all API access - a phone left with an old password will do this on its own. |
| Access level | None. Any employee that can log in can sync, and only ever within their own customer. |
| Error code | Message | Description |
|---|---|---|
| 401 | Authentication required | No Authorization: Basic header. |
| 401 | Invalid credentials | Malformed header, unknown e-mail, wrong password, or a passkey-only employee using a password. |
| 429 | too_many_requests | Rate limit for the token exceeded. |
| 403 | access_denied | Authenticated, but the resource belongs to another customer. |