The Side server exposes a webadmin based administration Rest API that follows overall the one implemented in Apache James.
This includes:
- Domain routes (Without domain alias support)
- User routes (only user crud, no support for delegation, from header, and JMAP identites).
- Webamin tasks routes backed by an InMemory, node local, task manager.
- Healthchecks routes
The following healthchecks are implemented:
Guice application lifecycle,LDAP User Server,RabbitMQ backend,OpenSearch Backend,Redis backend
It embeds a Prometheus compatible metric endpoint available via GET /metrics.
Additionally, the following endpoints are implemented, and are detailed below.
POST /add-revoked-token
Can be configured in OpenID identity provider in order to support back-channel logout.
Allow to oversee users that registered and used the DAV server.
GET /registeredUsers
Will return the list of registered users:
[
{
"email": "james@linagora.com",
"firstname": "James",
"lastname": "Bond",
"id": "248y230r2c"
},
...
]
POST /registeredUsers
{
"email": "james@linagora.com",
"firstname": "James",
"lastname": "Bond",
"id": "248y230r2c"
}
Returns 201 created status code.
HEAD /registeredUsers?email=james@linagora.com
Returns a status code of 200 if the user is registered, 400 instead.
DELETE /registeredUsers?email=james@linagora.com
Deletes the registered user matching the given email.
Status codes:
- 204 if deleted
- 400 if the email query parameter is missing
- 404 if the user does not exist
PATCH /registeredUsers?=248y230r2c
{
"email": "james2@linagora.com",
"firstname": "James2",
"lastname": "Bond2"
}
Will set the fields of the user to the following value.
Status code: 204
POST /registeredUsers/tasks?task=importFromLDAP&usersPerSecond=100
Will collect all users from LDAP and add them to twake calendar if not exist.
The query parameter usersPerSecond controls the concurrency. Defaults to 100.
This endpoint returns a webdmin task with the following additional information:
- processedUserCount: integer
- failedUserCount: integer
POST /registeredUsers?action=addMissingFields
Will add missing email and firstnames fields to existing registered users that don't have them.
This is a migration task to update legacy user documents.
The email field is extracted from accounts.emails[0].
The firstnames field is computed by splitting the firstname field by spaces (e.g., "Jean Paul" becomes ["Jean", "Paul"]).
This endpoint returns a webadmin task with the following additional information:
{
"type": "add-missing-fields",
"timestamp": "2025-11-24T10:15:30.00Z",
"processedUsers": 100,
"upgradedUsers": 25,
"errorCount": 0
}Where:
processedUsers: Total number of users processedupgradedUsers: Number of users that were updated with missing fieldserrorCount: Number of errors encountered during migration
Status codes:
201: Task successfully submitted400: Invalid action parameter
Only enabled if LDAP is configured.
POST /addressbook/domain-members?task=sync
Synchronizes LDAP members for all existing domains.
Optional query parameters:
ignoredDomains: [String] Comma-separated list of domains to exclude from synchronization
Example:
POST /addressbook/domain-members?task=sync&ignoredDomains=twake.app,example.org
POST /addressbook/domain-members/{domain}?task=sync
Synchronizes LDAP members only for the specified domain.
Both endpoints will return a webadmin task with the following additional information:
"additionalInformation": {
"type": "sync-domain-members-contacts-ldap-to-dav",
"domain": null,
"ignoredDomains": [ "twake.app" ],
"timestamp": "${json-unit.any-string}",
"addedCount": 1,
"addFailureContacts": [],
"updatedCount": 0,
"updateFailureContacts": [],
"deletedCount": 0,
"deleteFailureContacts": []
}
POST /calendars?task=reindex&eventsPerSecond=100&calendarsConcurrency=1
Will iterate all registered user calendars and reindex their events.
The query parameter eventsPerSecond controls the indexing rate. Defaults to 100.
The query parameter calendarsConcurrency controls how many calendars can be exported and parsed concurrently for a user. Defaults to 1.
This endpoint returns a webdmin task with the following additional information:
- processedEventCount: integer
- failedEventCount: integer
POST /calendars?task=scheduleAlarms?eventsPerSecond=100
Will iterate all registered user calendar and reschedule future alarms.
The query parameter eventsPerSecond controls the concurrency. Defaults to 100.
This endpoint returns a webdmin task with the following additional information:
- processedEventCount: integer
- failedEventCount: integer
Calendar events can be archived into a dedicated archival calendar using the Webadmin task framework. This operation is asynchronous and supports both all users and single user modes.
POST /calendars?task=archive
This endpoint iterates over all registered users and archives their calendar events matching the provided criteria.
| Parameter | Type | Optional | Description |
|---|---|---|---|
createdBefore |
duration | yes | Archive events whose DTSTAMP is before now minus the given duration (e.g. 5d, 12h, 1y) |
lastModifiedBefore |
duration | yes | Archive events whose LAST-MODIFIED is before now minus the given duration (e.g. 5d, 12h, 1y) |
masterDtStartBefore |
duration | yes | Archive events whose master DTSTART is before now minus the given duration (e.g. 5d, 12h, 1y) |
isRejected |
boolean | yes | When true, archive only events rejected by the user |
isNotRecurring |
boolean | yes | When true, archive only non-recurring events (events without RRULE, RDATE, or RECURRENCE-ID) |
eventsPerSecond |
integer | yes | Throttling parameter controlling processing speed (default: 100) |
- When no criteria parameter is provided, all events are archived.
- All criteria are combined using AND logic.
Example:
POST /calendars?task=archive&createdBefore=5d&isRejected=true
POST /calendars/{username}?task=archive
Archives calendar events only for the specified user.
The same query parameters are supported as for the all-users endpoint.
- When no criteria parameter is provided, all events of the user are archived.
- If the user does not exist, the request fails.
Example:
POST /calendars/john.doe@linagora.com?task=archive&lastModifiedBefore=30d
Both endpoints return a Webadmin task:
{
"taskId": "b7c8c3b0-5c5b-4e89-9e56-1a9f0d2e3a42"
}Task details can be retrieved via:
GET /tasks/{taskId}
GET /tasks/{taskId}/await
Example task result:
{
"status": "completed",
"additionalInformation": {
"archivedEventCount": 12,
"failedEventCount": 0,
"criteria": {
"createdBefore": "2025-12-22T00:00:00Z",
"lastModifiedBefore": null,
"masterDtStartBefore": null,
"rejectedOnly": true,
"isNotRecurring": true
}
}
}Where:
archivedEventCount: Number of events successfully archivedfailedEventCount: Number of events that failed to be archivedcriteria: Effective archival criteria applied for the task
Note: When using the single-user archival endpoint (POST /calendars/{username}?task=archive),
the task additionalInformation also contains a targetUser property indicating the archived user.
| Scenario | Status |
|---|---|
| Unknown user (single-user endpoint) | 404 Not Found |
| Invalid query parameter | 400 Bad Request |
All resource routes are scoped under a domain: /domains/{domain}/resources.
GET /domains/linagora.com/resources
Will list existing resources for that domain:
[
{
"name": "Resource name",
"deleted": false,
"description": "Descripting",
"id": "RESOURCE_ID_1",
"icon": "laptop",
"domain": "linagora.com",
"creator":"user1@linagora.com",
"administrators": [
{"email": "user1@linagora.com"},
{"email": "user2@linagora.com"}
]
}
]
Status codes:
- 200 when returning results
- 400 when domain name is malformed
- 404 when domain does not exist
GET /domains/linagora.com/resources/RESOURCE_ID
Will return the corresponding resource:
{
"name": "Resource name",
"deleted": false,
"description": "Descripting",
"id": "RESOURCE_ID",
"creator":"user1@linagora.com",
"icon": "laptop",
"domain": "linagora.com",
"administrators": [
{"email": "user1@linagora.com"},
{"email": "user2@linagora.com"}
]
}
Status codes: 404 if domain or resource not found (or resource belongs to another domain), 200 otherwise.
DELETE /domains/linagora.com/resources/RESOURCE_ID
Will mark the resource as deleted.
Status codes: 404 if domain or resource not found, 204 otherwise.
POST /domains/linagora.com/resources
{
"name": "Resource name",
"description": "Descripting",
"creator":"user1@linagora.com",
"icon": "laptop",
"administrators": [
{"email": "user1@linagora.com"},
{"email": "user2@linagora.com"}
]
}
The administrators field is optional. Omitting it (or providing an empty list) creates a resource with no administrator:
POST /domains/linagora.com/resources
{
"name": "Resource name",
"description": "Descripting",
"creator":"user1@linagora.com",
"icon": "laptop"
}
Will create the following resource.
Status codes:
- 201 if created. Location header contains the URL to read resource details
- 400 if invalid: creator/administrator do not exist, or extra fields / invalid JSON
- 404 if domain does not exist
A resource without administrator is not subject to the validation flow: any event request with this resource is automatically accepted.
Please note that resource administrators:
- are emailed upon events created that book the resource
- have delegation write access to the calendar of the resource
PATCH /domains/linagora.com/resources/RESOURCE_ID
{
"name": "Resource name 2",
"description": "Descripting 2",
"icon": "battery",
"administrators": [
{"email": "user2@linagora.com"}
]
}
Would update the resource accordingly. Each field is optional; omitting a field leaves it unchanged.
Status codes: 204 if updated, 400 if invalid (e.g. administrator not found), 404 if domain or resource not found.
Please note that resource administrators:
- are emailed upon events created that book the resource
- have delegation write access to the calendar of the resource. Removing an administrator revokes this delegation right.
POST /domains/linagora.com/resources?task=repositionWriteRights
Will iterate on each resource and ensure current administrators have delegation write access to the resource calendar, allowing them to accept, reject and counter events in the name of the resource.
Note that existing delegation write access granted to users no longer listed as administrators will not be revoked.
Domain-scoped mirror of the /registeredUsers routes, allowing multi-tenant safe access.
The global /registeredUsers routes remain available for global admin operations.
GET /domains/linagora.com/registeredUsers
Returns only users whose email belongs to linagora.com:
[
{
"email": "james@linagora.com",
"firstname": "James",
"lastname": "Bond",
"id": "248y230r2c"
}
]Optional ?email= filter to retrieve a single user:
GET /domains/linagora.com/registeredUsers?email=james@linagora.com
Returns 404 if the user does not exist or belongs to another domain.
Status codes:
- 200 on success
- 400 if domain name is malformed
- 404 if domain does not exist
POST /domains/linagora.com/registeredUsers
{
"email": "james@linagora.com",
"firstname": "James",
"lastname": "Bond"
}
The email domain must match the URL domain.
Status codes:
- 201 if created
- 400 if a required field is missing or the email domain does not match the URL domain
- 404 if domain does not exist
- 409 if a user with that email already exists
HEAD /domains/linagora.com/registeredUsers?email=james@linagora.com
HEAD /domains/linagora.com/registeredUsers?id=248y230r2c
Returns 200 if the user exists and belongs to that domain, 404 otherwise.
PATCH /domains/linagora.com/registeredUsers?id=248y230r2c
{
"email": "james2@linagora.com",
"firstname": "James2",
"lastname": "Bond2"
}
Status codes:
- 204 if updated
- 400 if required fields are missing
- 404 if the user does not exist or belongs to another domain
- 409 if the new email is already taken
DELETE /domains/linagora.com/registeredUsers?email=james@linagora.com
Deletes the registered user matching the given email when the user belongs to the domain in the URL.
Status codes:
- 204 if deleted
- 400 if the domain name is malformed or the email query parameter is missing
- 404 if the domain does not exist, the user does not exist, or the user belongs to another domain
Manage the list of administrators (admins) for each domain.
GET /domains/{domainName}/admins
Example:
GET /domains/linagora.com/admins
Returns the list of admins of the domain:
[
"user1@linagora.com",
"user2@linagora.com"
]Status codes:
200when returning the list (can be empty if the domain exists but has no admins).404when the domain does not exist.400whendomainNamehas an invalid format.
PUT /domains/{domainName}/admins/{username}
Example:
PUT /domains/linagora.com/admins/user1@linagora.com
Adds the user user1@linagora.com as an admin of domain linagora.com.
Status codes:
204if successfully added (idempotent: calling multiple times with the same user still returns204).404if the domain or user does not exist.400ifdomainNameorusernamehas an invalid format.
DELETE /domains/{domainName}/admins/{username}
Example:
DELETE /domains/linagora.com/admins/user1@linagora.com
Revokes administrator rights of user user1@linagora.com for domain linagora.com.
Status codes:
204if successful, even if the user exists but was not an admin (idempotent).404if the domain or user does not exist.400ifdomainNameorusernamehas an invalid format.
These routes provide domain-filtered access to the standard webadmin task management endpoints. They are intended for WebAdmin proxies that enforce multi-tenancy based on the domain in the URL. A task is only accessible if it belongs to the specified domain; otherwise a 404 is returned (to avoid leaking task IDs across domains).
The following task types are domain-scoped:
| Task type | Domain resolution |
|---|---|
calendar-archival (single-user) |
domain extracted from targetUser |
DeleteUserDataTask |
domain extracted from username |
sync-domain-members-contacts-ldap-to-dav (single-domain) |
domain field in additional information |
Multi-domain tasks (e.g. archive all users, sync all domains) are not attributed to any specific domain and are therefore not accessible through these routes.
GET /domains/{domain}/tasks/{taskId}
Returns the task execution details if the task belongs to the domain. Same response body as GET /tasks/{taskId}.
Status codes:
200: task found and belongs to the domain400: invalid task id format404: domain does not exist, task not found, or task does not belong to the domain
GET /domains/{domain}/tasks/{taskId}/await?timeout=3600s
Waits for the task to complete and returns the final execution details. Same semantics as GET /tasks/{taskId}/await.
Optional query parameter:
timeout: maximum wait duration (e.g.3600s,1d). Defaults to 365 days.
Status codes:
200: task completed and belongs to the domain400: invalid task id or timeout format404: domain does not exist, task not found, or task does not belong to the domain408: timeout reached before task completion
DELETE /domains/{domain}/tasks/{taskId}
Cancels the task if it belongs to the domain.
Status codes:
204: task cancelled (or already completed/cancelled)400: invalid task id format404: domain does not exist, task not found, or task does not belong to the domain
Allows deleting all data associated with a user. This is an asynchronous task that executes multiple deletion steps.
POST /users/{username}?action=deleteData
Example:
POST /users/james@linagora.com?action=deleteData
Will delete all data associated with the user james@linagora.com.
Query parameters:
action=deleteData(required): Triggers the deletion taskfromStep={stepName}(optional): Start execution from a specific step, skipping previous ones
Response:
Returns a task ID for async tracking:
{
"taskId": "464269f0-9314-11ef-a339-d76792bfb514"
}Status codes:
201: Task successfully submitted400: Invalid action or missing parameter
The deletion task executes the following steps in priority order:
| Step Name | Priority | Description |
|---|---|---|
DavCalendarDeletionTaskStep |
1 | Deletes user's calendars and calendar events |
DavContactDeletionTaskStep |
2 | Deletes user's contacts and address books |
CalendarSearchDeletionTaskStep |
10 | Removes indexed calendar events from OpenSearch |
OpenPaaSUserDeletionTaskStep |
1000 | Deletes user from the OpenPaaS user database |
To skip certain deletion steps, use the fromStep parameter:
POST /users/james@linagora.com?action=deleteData&fromStep=CalendarSearchDeletionTaskStep
This will skip DavCalendarDeletionTaskStep and DavContactDeletionTaskStep, starting directly from CalendarSearchDeletionTaskStep.