{
  "openapi": "3.0.0",
  "info": {
    "title": "LDAP-Rest API",
    "version": "1.0.0",
    "description": "RESTful API for LDAP management with LDAP-Rest"
  },
  "servers": [
    {
      "url": "http://localhost:8081",
      "description": "Development server"
    },
    {
      "url": "{protocol}://{host}:{port}",
      "description": "Custom server",
      "variables": {
        "protocol": {
          "default": "http",
          "enum": [
            "http",
            "https"
          ],
          "description": "Protocol scheme"
        },
        "host": {
          "default": "localhost",
          "description": "Host name"
        },
        "port": {
          "default": "8081",
          "description": "Port number"
        }
      }
    }
  ],
  "paths": {
    "/api/v1/authz-dynamic/reload": {
      "post": {
        "summary": "Reload the authz-dynamic token cache",
        "description": "Forces an immediate reload of all token entries from the LDAP\nbranch configured via `--authz-dynamic-base`.\n\n**Conditional registration:** this endpoint is only added to the\nrouter when the server was started with\n`--authz-dynamic-reload-endpoint true`.  It will be absent\n(404) otherwise.\n\n**Authentication required:** the request must carry a valid\n`Authorization: Bearer <token>` header that matches an existing\nentry in the token cache.  An unauthenticated or unrecognised\ntoken receives a `401` response.\n\nOn success the response includes the number of tokens now held\nin the in-memory cache.  This count can be used to verify that\nthe expected entries are present after adding or removing tokens\nin LDAP.\n",
        "responses": {
          "200": {
            "description": "Cache reloaded successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "tokens"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "tokens": {
                      "type": "integer",
                      "description": "Number of token entries now in the cache.",
                      "example": 3
                    }
                  }
                },
                "example": {
                  "success": true,
                  "tokens": 3
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Missing, invalid, or unrecognised bearer token."
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Authorization (Dynamic)"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/api/v1/authz/scope": {
      "get": {
        "summary": "Administration scope of the current user",
        "description": "Returns the branches the signed-in user administers, the permissions\nheld on each, and — per entity — whether they may create a new entry.\nA client uses it to show the scope explicitly and to hide actions that\nwould be refused.\n\nWith no authorization plugin judging the caller the server grants\neverything, and the answer says so through `unrestricted: true`.\nWhen plugins judge the caller and none of them can describe a scope\n(`authzPerRoute`, `authzDynamic`), the answer is `described: false`\nwith nothing in it: no model can say what will be granted, and a\nclient should not offer what it cannot know.\n\n`sources` names the authorization plugins judging the caller, and\n`source` the one this scope comes from — the first loaded, or the one\n`--authz-scope-source` names — so a client can tell a scope from one\nmodel among several from the server's whole answer.\n\n`transit` is the transit branch (`--authz-transit-branch`) when the\ndescribing plugin judges entries by their attachment, `null`\notherwise. Every administrator may put an entry they manage there,\nand claim one from it into a branch they write.\n\n`linksAnyDomain` says whether the caller may link an organization to\nany mail domain, rather than only to those an organization above it\nlinks: with `authzLinid1`, an administrator holding write on the top\norganization or on `--authz-global-organization`. `true` when\nunrestricted, `false` when the describing plugin cannot tell, and\nwhen the scope is not described. Like the rest of the scope, it is\nthe answer of `source` alone: another authorization plugin judging\nthe same caller may still refuse the link — `authzPerBranch`\ngrants it on the top organization only.\n\n`passwordPolicies` says whether the caller may list the password\npolicies (`GET /password-policies`), so a client offers them without\nasking for them and being refused: the judgement the describing\nplugin makes of a search of `--ppolicy-base` — with `authzLinid1`\nand `--authz-global-branches` holding it, only those who read that\nbranch. `true` when unrestricted, `false` when the scope is not\ndescribed, and absent when `core/ldap/passwordPolicy` is not loaded.\nIt is the answer of `source` too: another authorization plugin\njudging the same caller (`--authz-combine`) may still refuse the\nlisting, and so may a route-level gate (`authzPerRoute`).\n",
        "responses": {
          "200": {
            "description": "Scope of the current user.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "user",
                    "unrestricted",
                    "described",
                    "source",
                    "sources",
                    "branches",
                    "linksAnyDomain",
                    "entities"
                  ],
                  "properties": {
                    "user": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "unrestricted": {
                      "type": "boolean"
                    },
                    "described": {
                      "type": "boolean"
                    },
                    "source": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "sources": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "branches": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "dn"
                        ],
                        "properties": {
                          "dn": {
                            "type": "string"
                          },
                          "read": {
                            "type": "boolean"
                          },
                          "write": {
                            "type": "boolean"
                          },
                          "delete": {
                            "type": "boolean"
                          },
                          "name": {
                            "type": "string"
                          },
                          "path": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "transit": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "linksAnyDomain": {
                      "type": "boolean"
                    },
                    "passwordPolicies": {
                      "type": "boolean"
                    },
                    "entities": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "name",
                          "base",
                          "create"
                        ],
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "base": {
                            "type": "string"
                          },
                          "create": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "user": "uid=alice,ou=users,dc=example,dc=com",
                  "unrestricted": false,
                  "described": true,
                  "source": "authzLinid1",
                  "sources": [
                    "authzLinid1"
                  ],
                  "branches": [
                    {
                      "dn": "ou=Sales,ou=organization,dc=example,dc=com",
                      "read": true,
                      "write": true,
                      "delete": true
                    }
                  ],
                  "transit": "ou=Transit,ou=organization,dc=example,dc=com",
                  "linksAnyDomain": false,
                  "passwordPolicies": false,
                  "entities": [
                    {
                      "name": "users",
                      "base": "ou=users,dc=example,dc=com",
                      "create": true
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "No authenticated user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Authorization"
        ]
      }
    },
    "/api/v1/config": {
      "get": {
        "summary": "Get server configuration",
        "description": "Returns aggregated runtime configuration for all loaded plugins.\nPlugins that declare the `configurable` role expose a named block\nunder `features` via their `getConfigApiData()` method.  The\nresponse shape is open-ended (`additionalProperties: true`) because\nthe exact keys depend on which plugins are active.\n\nTypical consumers include browser-embedded LDAP editors that need\nto discover available schemas, endpoint prefixes, and feature flags\nwithout hard-coding server topology.\n",
        "responses": {
          "200": {
            "description": "Server configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Config"
                },
                "example": {
                  "apiPrefix": "/api",
                  "ldapBase": "dc=example,dc=com",
                  "version": "1.2.1",
                  "features": {
                    "ldapGroups": {
                      "enabled": true,
                      "base": "ou=groups,dc=example,dc=com",
                      "mainAttribute": "cn",
                      "objectClass": [
                        "top",
                        "groupOfNames"
                      ]
                    },
                    "static": {
                      "enabled": true,
                      "staticPath": "/static",
                      "endpoints": {
                        "schema": "/static/schemas/:name",
                        "schemaInSubdir": "/static/schemas/:dir/:name"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Configuration"
        ]
      }
    },
    "/api/hello": {
      "get": {
        "summary": "Demo hello-world endpoint",
        "description": "Exists exclusively as a minimal example for plugin development. The handler fires the `hello` hook and collects the results from every registered listener into `hookResults`. In a production deployment this plugin is normally disabled; enabling it is safe but adds no functional value.",
        "responses": {
          "200": {
            "description": "Greeting with hook results.",
            "content": {
              "application/json": {
                "schema": {
                  "description": "Response returned by the hello-world demo endpoint.",
                  "example": {
                    "message": "Hello",
                    "hookResults": []
                  },
                  "type": "object",
                  "required": [
                    "message",
                    "hookResults"
                  ],
                  "properties": {
                    "message": {
                      "description": "Static greeting string.",
                      "example": "Hello",
                      "type": "string"
                    },
                    "hookResults": {
                      "description": "Results collected from every function registered on the `hello` hook. Empty when no hook listeners are installed.",
                      "example": [],
                      "type": "array",
                      "items": {}
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Demo"
        ]
      }
    },
    "/api/v1/ldap/${entity.pluralName}/{id}/status": {
      "post": {
        "summary": "Set the lifecycle state of an account",
        "description": "Moves the account to one of the states its schema declares —\n`enabled`, `disabled`, or whatever else the deployment defines.\nThe state names come from the schema, so `GET /config` lists the\nones this directory accepts.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "State applied.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "state"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "state": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "state": "disabled"
                }
              }
            }
          },
          "400": {
            "description": "Unknown state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Entities"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "state"
                ],
                "properties": {
                  "state": {
                    "type": "string",
                    "description": "Name of a state declared by the schema.",
                    "example": "disabled"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/${entity.pluralName}/{id}/password": {
      "post": {
        "summary": "Reset the password of an account",
        "description": "Replaces the credential. When the body carries no `password`, one\nis generated and returned **once** in the response — it is never\nreadable afterwards. A `password` that is present must be a\nnon-empty string: an empty one is refused with a `400` rather\nthan silently replaced by a generated password the caller never\nsees. When the schema declares a `passwordReset` attribute,\n`forceChange` (true by default) also flags the account so the\ndirectory asks for a new password at next login.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Password replaced.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "generated",
                    "forceChange"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "generated": {
                      "type": "boolean"
                    },
                    "forceChange": {
                      "type": "boolean"
                    },
                    "password": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "generated": true,
                  "password": "xY3…"
                }
              }
            }
          },
          "400": {
            "description": "Empty, non-textual or too long password, a control character in it, or a non-boolean forceChange.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Entities"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "password": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256,
                    "description": "New password; generated when absent. An empty string and control characters are refused."
                  },
                  "forceChange": {
                    "type": "boolean",
                    "default": true,
                    "description": "Require a change at next login."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/bulk-import/{resource}/template.csv": {
      "get": {
        "summary": "Download CSV import template",
        "description": "Returns a CSV file with the header row listing all editable columns for\nthe `{resource}` type. Fixed, calculated, and organization-path columns\nare excluded because they are injected automatically during import.\n\nUse this template as the starting point for a bulk import file.\n",
        "parameters": [
          {
            "name": "resource",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "resource parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "CSV template file.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "cn,sn,givenName,mail,organizationDn\n"
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Resource type not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Bulk Import"
        ]
      }
    },
    "/api/v1/ldap/bulk-import/{resource}": {
      "post": {
        "summary": "Bulk import from CSV",
        "description": "Accepts a multipart/form-data upload containing a CSV file and optional\ncontrol flags. Each data row is mapped to an LDAP entry using the resource\nschema. Multi-valued attributes can be expressed with semicolons\n(`val1;val2;val3`). An `organizationDn` column (not listed in the template\nheader) may be provided to set the organization link and path attributes\nautomatically.\n\n**Dry-run mode** (`dryRun=true`) validates, judges and counts rows without\nwriting to LDAP: a row the caller may not write fails as in an import.\n`continueOnError` (default `true`) keeps processing subsequent rows\nafter an individual row fails.\n",
        "parameters": [
          {
            "name": "resource",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "resource parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Import result summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkImportResult"
                },
                "example": {
                  "success": true,
                  "total": 10,
                  "created": 9,
                  "updated": 0,
                  "skipped": 0,
                  "failed": 1,
                  "errors": [
                    {
                      "line": 7,
                      "identifier": "charlie",
                      "error": "Missing required attribute: mail"
                    }
                  ],
                  "details": {
                    "duration": "0.8s",
                    "linesProcessed": 10
                  }
                }
              }
            }
          },
          "400": {
            "description": "No file uploaded or malformed request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "tags": [
          "Bulk Import"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "CSV file to import (max 10 MB by default)."
                  },
                  "dryRun": {
                    "type": "boolean",
                    "description": "When true, validate and count rows without writing to LDAP.\n"
                  },
                  "updateExisting": {
                    "type": "boolean",
                    "description": "When true, existing entries are updated (replace). When false\n(default), they are skipped.\n"
                  },
                  "continueOnError": {
                    "type": "boolean",
                    "default": true,
                    "description": "Continue processing subsequent rows after a row-level error.\n"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/groups": {
      "get": {
        "summary": "List groups",
        "description": "Returns every group under the configured group base. The optional\n`match` query is matched against the attributes named by\n`attribute`, as a substring — the same semantics as the flat entity\nlists. Without `attribute` it is either a raw LDAP filter (when it\ncontains `=`) or an exact value of the RDN attribute.\n",
        "parameters": [
          {
            "in": "query",
            "name": "match",
            "schema": {
              "type": "string"
            },
            "description": "Substring to look for, LDAP filter, or exact value.",
            "example": "admin"
          },
          {
            "in": "query",
            "name": "attribute",
            "schema": {
              "type": "string"
            },
            "description": "LDAP attribute name to match `match` against, as a substring.\nSeveral may be given, separated by commas; a group matching any\nof them is returned. Each name must be indexed for a substring\nsearch, or the directory scans the branch.\n",
            "example": "cn,mail"
          },
          {
            "in": "query",
            "name": "attributes",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated list of attributes to return.",
            "example": "cn,description,member"
          }
        ],
        "responses": {
          "200": {
            "description": "Group list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Group"
                  }
                },
                "example": [
                  {
                    "dn": "cn=admins,ou=groups,dc=example,dc=com",
                    "cn": "admins",
                    "description": "Server administrators",
                    "member": [
                      "uid=alice,ou=users,dc=example,dc=com"
                    ]
                  },
                  {
                    "dn": "cn=editors,ou=groups,dc=example,dc=com",
                    "cn": "editors",
                    "description": "Content editors",
                    "member": []
                  }
                ]
              }
            }
          },
          "400": {
            "description": "Invalid `match` query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Groups"
        ]
      },
      "post": {
        "summary": "Create group",
        "responses": {
          "200": {
            "description": "Group created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Groups"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupCreate"
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/groups/{cn}": {
      "get": {
        "summary": "Get group by CN",
        "description": "The `:cn` segment may be either the group's RDN value (e.g.\n`admins`) or its full DN. Returns the raw LDAP entry.\n",
        "parameters": [
          {
            "name": "cn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "cn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Group entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Group"
                },
                "example": {
                  "dn": "cn=admins,ou=groups,dc=example,dc=com",
                  "cn": "admins",
                  "description": "Server administrators",
                  "member": [
                    "uid=alice,ou=users,dc=example,dc=com"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Group not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Groups"
        ]
      },
      "delete": {
        "summary": "Delete group",
        "parameters": [
          {
            "name": "cn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "cn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Group deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Group not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Groups"
        ]
      },
      "put": {
        "summary": "Modify group",
        "parameters": [
          {
            "name": "cn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "cn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Group updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Group not found."
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Groups"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupModify"
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/groups/{cn}/members": {
      "post": {
        "summary": "Add members to group",
        "parameters": [
          {
            "name": "cn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "cn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Members added.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "A member does not exist or is not a DN; none is added."
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Groups"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "member"
                ],
                "properties": {
                  "member": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    ],
                    "description": "One DN or a list of DNs to add."
                  }
                }
              },
              "example": {
                "member": [
                  "uid=carol,ou=users,dc=example,dc=com",
                  "uid=dave,ou=users,dc=example,dc=com"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/groups/{cn}/members/{member}": {
      "delete": {
        "summary": "Remove member from group",
        "description": "`:member` is the URL-encoded DN of the member to remove.\n",
        "parameters": [
          {
            "name": "cn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "cn parameter"
          },
          {
            "name": "member",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "member parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Member removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "The member is the placeholder (`--group-dummy-user`), which cannot be removed."
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Group or member not found."
          },
          "409": {
            "description": "The member is the last one of the group and no placeholder is configured to take its place."
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Groups"
        ]
      }
    },
    "/api/v1/ldap/groups/{cn}/move": {
      "post": {
        "summary": "Move group to another organization",
        "description": "Only registered when both\n`--ldap-organization-link-attribute` and\n`--ldap-organization-path-attribute` are configured.\n",
        "parameters": [
          {
            "name": "cn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "cn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Group moved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Groups"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "targetOrgDn"
                ],
                "properties": {
                  "targetOrgDn": {
                    "type": "string",
                    "description": "DN of the destination organization."
                  }
                }
              },
              "example": {
                "targetOrgDn": "ou=engineering,ou=organizations,dc=example,dc=com"
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/groups/{cn}/rename": {
      "post": {
        "summary": "Rename group (change CN)",
        "parameters": [
          {
            "name": "cn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "cn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Group renamed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Groups"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "newCn"
                ],
                "properties": {
                  "newCn": {
                    "type": "string",
                    "description": "New CN value (RDN attribute)."
                  }
                }
              },
              "example": {
                "newCn": "site-admins"
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/organizations/top": {
      "get": {
        "summary": "Get top organization",
        "description": "Returns the single root organizational unit configured via\n`--ldap-top-organization`. Authorization plugins may filter the result\nto only the branches the caller has access to.\n",
        "responses": {
          "200": {
            "description": "Top organization entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Organization"
                },
                "example": {
                  "dn": "ou=organizations,dc=example,dc=com",
                  "ou": "organizations",
                  "description": "Root organization",
                  "objectClass": [
                    "top",
                    "organizationalUnit"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Top organization not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Organizations"
        ]
      }
    },
    "/api/v1/ldap/organizations/{dn}": {
      "get": {
        "summary": "Get organization by DN",
        "description": "The `:dn` segment must be the URL-encoded fully-qualified DN of the\norganizational unit (e.g. `ou=Engineering%2Cou=organizations%2Cdc%3Dexample%2Cdc%3Dcom`).\nReturns a 404 when the DN does not exist or does not have\n`objectClass=organizationalUnit`.\n",
        "parameters": [
          {
            "name": "dn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "dn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Organization entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Organization"
                },
                "example": {
                  "dn": "ou=Engineering,ou=organizations,dc=example,dc=com",
                  "ou": "Engineering",
                  "description": "Software engineering division",
                  "objectClass": [
                    "top",
                    "organizationalUnit"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The DN cannot be parsed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Organization not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Organizations"
        ]
      },
      "put": {
        "summary": "Modify organization",
        "description": "Partial attribute update. Each key in the body replaces the existing\nvalue for that attribute. The `ou` RDN and the organization path/link\nattributes cannot be changed through this endpoint.\n",
        "parameters": [
          {
            "name": "dn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "dn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Organization updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Organization not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Organizations"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrganizationModify"
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete organization",
        "description": "Deletes the organizational unit identified by `:dn`. The operation is\nrejected with **409 Conflict** when any entry still references this\norganization via the organization-link attribute (i.e. the OU is not\nempty).\n",
        "parameters": [
          {
            "name": "dn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "dn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Organization deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Organization not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Organization is not empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Organizations"
        ]
      }
    },
    "/api/v1/ldap/organizations/{dn}/subnodes": {
      "get": {
        "summary": "List subnodes of an organization",
        "description": "Returns the direct child organizational units of `:dn`, followed by\nup to `ldap_organization_max_subnodes` (default 50) entries (users,\ngroups) whose organization-link attribute points to it.\n\nTwo things bound the answer, and each appends a sentinel entry with\n`objectClass: [moreIndicator]` and `_isMoreIndicator: \"true\"`, which\na client must drop before treating the rest as entries:\n\n- the cap on the attached entries, whose sentinel is at\n  `more-<dn>` and carries `_totalCount` and `_displayedCount`;\n- a directory refusing to list a branch in one answer, whose\n  sentinel is at `more-organizations-<dn>` and carries\n  `_displayedCount` alone — nothing counted the rest.\n\nChild organizations are not capped.\n",
        "parameters": [
          {
            "name": "dn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "dn parameter"
          },
          {
            "in": "query",
            "name": "objectClass",
            "schema": {
              "type": "string"
            },
            "description": "Filter linked entities by objectClass (e.g. `inetOrgPerson`,\n`groupOfNames`). Pass `organizationalUnit` to return only child OUs.\n",
            "example": "inetOrgPerson"
          }
        ],
        "responses": {
          "200": {
            "description": "List of subnodes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OrgSummary"
                  }
                },
                "example": [
                  {
                    "dn": "ou=Backend,ou=Engineering,ou=organizations,dc=example,dc=com",
                    "ou": "Backend",
                    "objectClass": [
                      "top",
                      "organizationalUnit"
                    ]
                  },
                  {
                    "dn": "uid=alice,ou=users,dc=example,dc=com",
                    "uid": "alice",
                    "cn": "Alice Smith",
                    "objectClass": [
                      "top",
                      "inetOrgPerson"
                    ]
                  }
                ]
              }
            }
          },
          "400": {
            "description": "The DN cannot be parsed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Organization not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Organizations"
        ]
      }
    },
    "/api/v1/ldap/organizations/{dn}/subnodes/search": {
      "get": {
        "summary": "Search subnodes of an organization",
        "description": "Full-text search across child OUs and linked entries (users, groups)\nof `:dn`. The `q` parameter is matched against `ou`, `description`,\n`uid`, `cn`, `sn`, `givenName`, and `mail`.\n\nMatching attached entries are capped at\n`ldap_organization_max_subnodes` (default 50), and the same sentinel\nentries as `/subnodes` say when the answer is partial: a client must\ndrop whatever carries `_isMoreIndicator` before treating the rest as\nentries.\n",
        "parameters": [
          {
            "name": "dn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "dn parameter"
          },
          {
            "in": "query",
            "name": "q",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Search query matched against common attributes.",
            "example": "alice"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching entries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OrgSummary"
                  }
                },
                "example": [
                  {
                    "dn": "uid=alice,ou=users,dc=example,dc=com",
                    "uid": "alice",
                    "cn": "Alice Smith",
                    "mail": "alice@example.com"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "Query parameter `q` missing, or the DN cannot be parsed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Organizations"
        ]
      }
    },
    "/api/v1/ldap/organizations/{dn}/search": {
      "get": {
        "summary": "Search organizations across a subtree",
        "description": "Searches the organizations under `:dn`, at any depth, and `:dn`\nitself. Unlike `/subnodes/search`, which only looks at the direct\nchildren and mixes in the linked users and groups, this answers\norganizations only: it is the call behind a department picker over\na tree too wide to list.\n\n`q` is matched against `ou`, `description` and, when\n`ldap_organization_path_attribute` is set, the path attribute, so a\nquery such as `Government / lin` finds a department by its path.\n\nAt most `ldap_organization_max_subnodes` (default 50) organizations\nare returned. A partial answer ends with a sentinel entry at\n`more-organizations-<dn>` carrying `_isMoreIndicator: \"true\"`, which\na client must drop before treating the rest as organizations, and\nwhich says why the answer is partial:\n\n- more organizations matched than the cap: it carries\n  `_totalCount` and `_displayedCount`, and a narrower query helps;\n- the directory refused to list them all: it carries\n  `_displayedCount` alone — nothing counted the rest — and the fix\n  is the size limit of the account ldap-rest binds as.\n",
        "parameters": [
          {
            "name": "dn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "dn parameter"
          },
          {
            "in": "query",
            "name": "q",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Search query matched against name, description and path.",
            "example": "lin"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching organizations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OrgSummary"
                  }
                },
                "example": [
                  {
                    "dn": "ou=linagora.com,ou=Government,ou=organization,dc=example,dc=com",
                    "ou": "linagora.com"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "Query parameter `q` missing, or the DN cannot be parsed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Organizations"
        ]
      }
    },
    "/api/v1/ldap/organizations": {
      "post": {
        "summary": "Create organization",
        "description": "Creates a new organizational unit. The `ou` field becomes the RDN.\n`parentDn` controls where in the tree the node is placed; it defaults\nto the configured top organization.\n",
        "responses": {
          "200": {
            "description": "Organization created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "Validation error (missing `ou`, invalid DN value, …).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Organization already exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Organizations"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrganizationCreate"
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/organizations/{dn}/move": {
      "post": {
        "summary": "Move organization to another parent",
        "description": "Performs an LDAP `modifyDN` to relocate the organizational unit `:dn`\nunder `targetOrgDn`. The new DN is returned in the response.\nMoving into a descendant or the current parent is rejected.\n",
        "parameters": [
          {
            "name": "dn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "dn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Organization moved. Returns the new DN.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "newDn": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "newDn": "ou=Engineering,ou=divisions,ou=organizations,dc=example,dc=com"
                }
              }
            }
          },
          "400": {
            "description": "Invalid target (circular move, same location, …).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Source or target organization not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Organizations"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "targetOrgDn"
                ],
                "properties": {
                  "targetOrgDn": {
                    "type": "string",
                    "description": "DN of the destination organizational unit."
                  }
                }
              },
              "example": {
                "targetOrgDn": "ou=divisions,ou=organizations,dc=example,dc=com"
              }
            }
          }
        }
      }
    },
    "/api/v1/password-policy": {
      "get": {
        "summary": "Get password policy",
        "description": "Returns the active OpenLDAP ppolicy configuration read from the DN\nconfigured via `--ppolicy-default-dn`, or discovered by searching for\n`objectClass=pwdPolicy` when that option is absent.\nResults are cached for 60 seconds.\n",
        "responses": {
          "200": {
            "description": "Active password policy.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PasswordPolicy"
                },
                "example": {
                  "dn": "cn=default,ou=policies,dc=example,dc=com",
                  "pwdMinLength": 12,
                  "pwdMaxAge": 7776000,
                  "pwdMaxFailure": 5,
                  "pwdLockout": true,
                  "pwdLockoutDuration": 300,
                  "pwdMustChange": true,
                  "pwdAllowUserChange": true,
                  "pwdInHistory": 5,
                  "pwdGraceAuthNLimit": 0
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "An authorization plugin keeps the policies from the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Password Policy"
        ]
      }
    },
    "/api/v1/users/{id}/password-status": {
      "get": {
        "summary": "Get user password status",
        "description": "Returns the current password state for user `:id`. The `id` may be\na plain `uid` value or a full DN. The response is computed from the\nuser's ppolicy operational attributes combined with the active policy.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Password status for the user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PasswordStatus"
                },
                "example": {
                  "dn": "uid=alice,ou=users,dc=example,dc=com",
                  "passwordSet": true,
                  "lastChanged": "2026-01-15T10:00:00.000Z",
                  "expiresAt": "2026-04-15T10:00:00.000Z",
                  "daysUntilExpiration": 80,
                  "isExpired": false,
                  "isExpiringSoon": false,
                  "mustChange": false,
                  "isLocked": false,
                  "lockedAt": null,
                  "failureCount": 0,
                  "graceLoginsUsed": 0,
                  "policy": "cn=default,ou=policies,dc=example,dc=com"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "User not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Password Policy"
        ]
      }
    },
    "/api/v1/users/{id}/unlock": {
      "post": {
        "summary": "Unlock user account",
        "description": "Removes the `pwdAccountLockedTime` and `pwdFailureTime` operational\nattributes from the user entry, effectively clearing the lockout state.\nIf the account was not locked the operation still succeeds (idempotent).\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Account unlocked (or was already unlocked).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "message"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "message": "Account unlocked"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "User not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Password Policy"
        ]
      }
    },
    "/api/v1/password-policy/expiring-soon": {
      "get": {
        "summary": "List accounts with expiring passwords",
        "description": "Scans the user base for accounts whose password will expire within\n`days` days. Results are sorted ascending by `daysUntilExpiration`.\nEach account is judged by its own policy (`pwdPolicySubentry`, else\nthe default one); those whose policy sets no `pwdMaxAge` never appear.\n",
        "parameters": [
          {
            "in": "query",
            "name": "days",
            "schema": {
              "type": "integer",
              "default": 14
            },
            "description": "Warning window in days (defaults to `--ppolicy-warn-days` or 14).",
            "example": 7
          }
        ],
        "responses": {
          "200": {
            "description": "List of expiring accounts with their warning window.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "warningDays": {
                      "type": "integer",
                      "example": 7
                    },
                    "users": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ExpiringAccount"
                      }
                    }
                  }
                },
                "example": {
                  "warningDays": 7,
                  "users": [
                    {
                      "dn": "uid=bob,ou=users,dc=example,dc=com",
                      "uid": "bob",
                      "displayName": "Bob Builder",
                      "mail": "bob@example.com",
                      "expiresAt": "2026-05-01T08:00:00.000Z",
                      "daysUntilExpiration": 6
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Password Policy"
        ]
      }
    },
    "/api/v1/password-policy/locked-accounts": {
      "get": {
        "summary": "List locked accounts",
        "description": "Returns all user entries in the user base that have a\n`pwdAccountLockedTime` attribute set, regardless of whether the\nlockout has expired. Use the `/unlock` endpoint to clear individual\nlockouts.\n",
        "responses": {
          "200": {
            "description": "List of locked accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accounts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LockedAccount"
                      }
                    }
                  }
                },
                "example": {
                  "accounts": [
                    {
                      "dn": "uid=charlie,ou=users,dc=example,dc=com",
                      "uid": "charlie",
                      "displayName": "Charlie Chaplin",
                      "mail": "charlie@example.com",
                      "lockedAt": "2026-04-24T14:30:00.000Z",
                      "failureCount": 5
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Password Policy"
        ]
      }
    },
    "/api/v1/password-policies": {
      "get": {
        "summary": "List password policies",
        "description": "Returns every `pwdPolicy` entry under `--ppolicy-base` (else\n`--ldap-base`), sorted by `cn`, and the attributes a policy can be\ngiven: those the API writes that the directory's schema defines.\n",
        "responses": {
          "200": {
            "description": "The password policies.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "policies": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PasswordPolicyEntry"
                      }
                    },
                    "attributes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "example": [
                        "pwdMinAge",
                        "pwdMaxAge",
                        "pwdInHistory"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Password Policy"
        ]
      },
      "post": {
        "summary": "Create a password policy",
        "description": "Creates `cn=<cn>` under `--ppolicy-base` (else `--ldap-base`), applied\nto `userPassword`. The body holds `cn` and the attributes to set.\n",
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "201": {
            "description": "The policy created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PasswordPolicyEntry"
                }
              }
            }
          },
          "400": {
            "description": "Missing `cn`, unknown attribute or wrongly typed value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "`--ppolicy-base` does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A policy of that name exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Password Policy"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/PasswordPolicyChanges"
                  },
                  {
                    "type": "object",
                    "required": [
                      "cn"
                    ],
                    "properties": {
                      "cn": {
                        "type": "string",
                        "example": "software"
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/password-policies/{cn}": {
      "get": {
        "summary": "Get a password policy",
        "parameters": [
          {
            "name": "cn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "cn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "The policy.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PasswordPolicyEntry"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "No policy of that name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Password Policy"
        ]
      },
      "patch": {
        "summary": "Change a password policy",
        "description": "Sets the attributes the body names and removes those given `null`.\nWriting `pwdCheckModuleArg` or `pwdUseCheckModule` gives the entry\nthe `pwdPolicyChecker` class when it lacks it.\n",
        "parameters": [
          {
            "name": "cn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "cn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "The policy as changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PasswordPolicyEntry"
                }
              }
            }
          },
          "400": {
            "description": "Unknown or read-only attribute, or wrongly typed value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "No policy of that name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Password Policy"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordPolicyChanges"
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a password policy",
        "description": "Refused while the policy is the default one or an entry's\n`pwdPolicySubentry` names it.\n",
        "parameters": [
          {
            "name": "cn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "cn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Policy deleted.",
            "content": {
              "application/json": {
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "No policy of that name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The policy is the default one, or is still applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Password Policy"
        ]
      }
    },
    "/api/v1/users/{id}/password-policy": {
      "put": {
        "summary": "Assign a password policy to an account",
        "description": "Sets the account's `pwdPolicySubentry` to the policy named `policy`,\nor removes it when `policy` is `null`, the account then getting the\ndefault policy. The `id` may be a plain `uid` value or a full DN.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "The policy now applied.",
            "content": {
              "application/json": {
                "example": {
                  "dn": "uid=build,ou=users,dc=example,dc=com",
                  "policy": "cn=software,ou=policies,dc=example,dc=com"
                }
              }
            }
          },
          "400": {
            "description": "Missing `policy`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "No such account, or no policy of that name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Password Policy"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "policy"
                ],
                "properties": {
                  "policy": {
                    "type": "string",
                    "nullable": true,
                    "example": "software"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/password/validate": {
      "post": {
        "summary": "Validate password complexity",
        "description": "Validates a candidate password against the locally-configured complexity\nrules (`--ppolicy-min-length`, `--ppolicy-require-uppercase`, etc.).\nThis endpoint is only registered when `--ppolicy-validate-complexity` is\nenabled. It does **not** call LDAP — it runs a local rule check only.\n",
        "responses": {
          "200": {
            "description": "Validation result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PasswordValidationResponse"
                },
                "example": {
                  "valid": false,
                  "errors": [
                    "Minimum 12 characters required",
                    "At least one special character required"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Missing `password` field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Password Policy"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordValidationRequest"
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/raw/bases": {
      "get": {
        "summary": "List the exposed bases",
        "description": "Entry points of the low-level API: the subtrees the server is\nconfigured to expose (`--ldap-raw-base`, defaulting to `--ldap-base`).\n",
        "responses": {
          "200": {
            "description": "Exposed bases.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "bases": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                },
                "example": {
                  "bases": [
                    "dc=example",
                    "dc=com"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "ldapRaw"
        ]
      }
    },
    "/api/v1/ldap/raw/rootdse": {
      "get": {
        "summary": "Read the root DSE",
        "description": "Server capabilities: naming contexts, supported controls, extensions\nand the DN of the subschema entry. Read with the service account, as\ndirectory metadata rather than user data.\n",
        "responses": {
          "200": {
            "description": "Root DSE.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RawEntry"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Root DSE is not readable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "ldapRaw"
        ]
      }
    },
    "/api/v1/ldap/raw/schema": {
      "get": {
        "summary": "Read the directory schema",
        "description": "Parsed content of the subschema entry: object classes (with their\n`MUST`/`MAY` attributes and `SUP` chain), attribute types, syntaxes\nand matching rules. Cached for `--ldap-raw-schema-cache-ttl` seconds.\n",
        "responses": {
          "200": {
            "description": "Parsed schema.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "objectClasses": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "attributeTypes": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "syntaxes": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "matchingRules": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Subschema entry not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "ldapRaw"
        ]
      }
    },
    "/api/v1/ldap/raw/search": {
      "get": {
        "summary": "Search the directory",
        "description": "Raw LDAP search. `base` must be inside one of the exposed bases, and\nthe usual authorization hooks apply on top of that.\n",
        "parameters": [
          {
            "in": "query",
            "name": "base",
            "schema": {
              "type": "string"
            },
            "description": "Search base. Defaults to the first exposed base.",
            "example": "ou=users,dc=example,dc=com"
          },
          {
            "in": "query",
            "name": "scope",
            "schema": {
              "type": "string",
              "enum": [
                "base",
                "one",
                "sub"
              ]
            },
            "description": "Search scope (default `sub`)."
          },
          {
            "in": "query",
            "name": "filter",
            "schema": {
              "type": "string"
            },
            "description": "LDAP filter (default `(objectClass=*)`).",
            "example": "(uid=alice)"
          },
          {
            "in": "query",
            "name": "attributes",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated attribute list (default `*`).",
            "example": "uid,cn,mail"
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer"
            },
            "description": "Maximum number of entries, capped by `--ldap-raw-max-results`.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching entries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "entries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RawEntry"
                      }
                    },
                    "truncated": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid scope, invalid limit, unparseable LDAP filter, or a\nfilter testing a hidden attribute — including an extensible\nmatch without an attribute, which runs over every attribute.\nThe message names an unparseable filter and recalls the expected\nform, e.g. `Invalid LDAP filter \"gov\"`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Base outside the exposed subtrees.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such base, or one the caller cannot read: both answer alike.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          },
          "503": {
            "description": "The directory schema could not be read, so a hidden attribute\ncannot be matched under its OID or aliases; the search is\nrefused rather than answered unmasked.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "tags": [
          "ldapRaw"
        ]
      }
    },
    "/api/v1/ldap/raw/children/{dn}": {
      "get": {
        "summary": "List the children of an entry",
        "description": "Direct children (scope `one`) of the URL-encoded `:dn`, as needed to\nexpand a tree node. At most `--ldap-raw-max-results` entries are\nreturned; `truncated` tells whether the branch holds more.\n",
        "parameters": [
          {
            "name": "dn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "dn parameter"
          },
          {
            "in": "query",
            "name": "children",
            "schema": {
              "type": "boolean"
            },
            "description": "When set, each child is probed for children of its own so the UI\ncan decide whether to draw an expand arrow. Costs one extra\nsearch per child.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Direct children, sorted by RDN.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "children": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RawChild"
                      }
                    },
                    "truncated": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "DN outside the exposed subtrees.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "ldapRaw"
        ]
      }
    },
    "/api/v1/ldap/raw/entry/{dn}": {
      "get": {
        "summary": "Read an entry",
        "description": "Full content of the URL-encoded `:dn`, operational attributes\nincluded. Binary values are base64-encoded and flagged as such.\nAttributes listed in `--ldap-raw-hidden-attribute` are omitted.\n",
        "parameters": [
          {
            "name": "dn",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "dn parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "The entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RawEntry"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "DN outside the exposed subtrees.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entry not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          },
          "503": {
            "description": "The directory schema could not be read, so a hidden attribute\ncannot be matched under its OID or aliases; the entry is not\nserved rather than answered unmasked.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "tags": [
          "ldapRaw"
        ]
      }
    },
    "${prefix}/Users": {
      "get": {
        "summary": "List users",
        "description": "Returns a paginated list of SCIM User resources matching the optional\nfilter. Pagination is controlled by `startIndex` (1-based) and `count`.\nThe response always uses the `ListResponse` envelope.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "in": "query",
            "name": "filter",
            "schema": {
              "type": "string"
            },
            "description": "SCIM filter expression (RFC 7644 §3.4.2.2), e.g.\n`userName eq \"bjensen\"` or `emails.value co \"@example.com\"`.\n",
            "example": "userName eq \"bjensen\""
          },
          {
            "in": "query",
            "name": "startIndex",
            "schema": {
              "type": "integer",
              "default": 1
            },
            "description": "1-based index of the first result to return.",
            "example": 1
          },
          {
            "in": "query",
            "name": "count",
            "schema": {
              "type": "integer"
            },
            "description": "Maximum number of results to return per page.",
            "example": 10
          },
          {
            "in": "query",
            "name": "attributes",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated SCIM attribute names to return, in place of the\ndefault set (RFC 7644 section 3.9). Mutually exclusive with\n`excludedAttributes`.\n",
            "example": "userName,emails"
          },
          {
            "in": "query",
            "name": "excludedAttributes",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated SCIM attribute names to exclude from the response.",
            "example": "password,x509Certificates"
          },
          {
            "in": "query",
            "name": "sortBy",
            "schema": {
              "type": "string"
            },
            "description": "Attribute to sort results by (parsed but sort not guaranteed server-side)."
          },
          {
            "in": "query",
            "name": "sortOrder",
            "schema": {
              "type": "string",
              "enum": [
                "ascending",
                "descending"
              ]
            },
            "description": "Sort direction."
          }
        ],
        "responses": {
          "200": {
            "description": "User list.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimListResponse"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:ListResponse"
                  ],
                  "totalResults": 2,
                  "startIndex": 1,
                  "itemsPerPage": 2,
                  "Resources": [
                    {
                      "schemas": [
                        "urn:ietf:params:scim:schemas:core:2.0:User"
                      ],
                      "id": "2819c223-7f76-453a-919d-413861904646",
                      "userName": "bjensen",
                      "displayName": "Babs Jensen",
                      "active": true,
                      "emails": [
                        {
                          "value": "bjensen@example.com",
                          "type": "work",
                          "primary": true
                        }
                      ]
                    },
                    {
                      "schemas": [
                        "urn:ietf:params:scim:schemas:core:2.0:User"
                      ],
                      "id": "c75ad752-64ae-4823-840d-ffa80929976c",
                      "userName": "jsmith",
                      "displayName": "John Smith",
                      "active": true
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid filter expression.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "400",
                  "scimType": "invalidFilter",
                  "detail": "Filter parse error: unexpected token near \"eq\""
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ]
      },
      "post": {
        "summary": "Create user",
        "description": "Creates a new SCIM User resource. The `userName` attribute is required.\nReturns the created resource with status 201 and a `Location` header.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "201": {
            "description": "User created.",
            "headers": {
              "Location": {
                "description": "URI of the created resource (RFC 7644 section 3.1). Absolute\nwhen `--scim-base-url` is set, prefix-relative otherwise.\n",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimUser"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:schemas:core:2.0:User"
                  ],
                  "id": "2819c223-7f76-453a-919d-413861904646",
                  "userName": "bjensen",
                  "displayName": "Babs Jensen",
                  "active": true,
                  "meta": {
                    "resourceType": "User",
                    "created": "2024-01-10T09:00:00Z",
                    "location": "https://example.com/scim/v2/Users/2819c223-7f76-453a-919d-413861904646"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request body is missing or malformed.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "400",
                  "scimType": "invalidSyntax",
                  "detail": "Missing body"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "A user with that userName already exists.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "409",
                  "scimType": "uniqueness",
                  "detail": "User bjensen already exists"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/scim+json": {
              "schema": {
                "$ref": "#/components/schemas/ScimUser"
              },
              "example": {
                "schemas": [
                  "urn:ietf:params:scim:schemas:core:2.0:User"
                ],
                "userName": "bjensen",
                "externalId": "701984",
                "name": {
                  "familyName": "Jensen",
                  "givenName": "Barbara"
                },
                "displayName": "Babs Jensen",
                "emails": [
                  {
                    "value": "bjensen@example.com",
                    "type": "work",
                    "primary": true
                  }
                ],
                "active": true
              }
            }
          }
        }
      }
    },
    "${prefix}/Users/{id}": {
      "get": {
        "summary": "Get user by ID",
        "description": "Returns a single SCIM User resource. The `:id` value is the server-\nassigned opaque identifier returned when the resource was created.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "in": "query",
            "name": "attributes",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated attribute names to return, in place of the default\nset (RFC 7644 section 3.9). Mutually exclusive with\n`excludedAttributes`.\n",
            "example": "userName,name.familyName"
          },
          {
            "in": "query",
            "name": "excludedAttributes",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated attribute names to omit from the default set.",
            "example": "emails"
          }
        ],
        "responses": {
          "200": {
            "description": "User resource.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimUser"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:schemas:core:2.0:User"
                  ],
                  "id": "2819c223-7f76-453a-919d-413861904646",
                  "externalId": "701984",
                  "userName": "bjensen",
                  "name": {
                    "formatted": "Ms. Barbara J Jensen III",
                    "familyName": "Jensen",
                    "givenName": "Barbara",
                    "honorificPrefix": "Ms.",
                    "honorificSuffix": "III"
                  },
                  "displayName": "Babs Jensen",
                  "active": true,
                  "emails": [
                    {
                      "value": "bjensen@example.com",
                      "type": "work",
                      "primary": true
                    }
                  ],
                  "meta": {
                    "resourceType": "User",
                    "created": "2024-01-10T09:00:00Z",
                    "lastModified": "2024-01-15T12:30:00Z",
                    "location": "https://example.com/scim/v2/Users/2819c223-7f76-453a-919d-413861904646"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "User not found.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "404",
                  "detail": "Resource 2819c223-7f76-453a-919d-413861904646 not found."
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ]
      },
      "put": {
        "summary": "Replace user",
        "description": "Replaces all attributes of an existing SCIM User resource with the\nvalues in the request body (full replacement, RFC 7644 §3.5.1).\nAny attributes omitted from the body are cleared on the server.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "User replaced.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimUser"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:schemas:core:2.0:User"
                  ],
                  "id": "2819c223-7f76-453a-919d-413861904646",
                  "userName": "bjensen",
                  "displayName": "Babs Jensen",
                  "active": true
                }
              }
            }
          },
          "400": {
            "description": "Request body is missing or malformed.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "User not found.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "404",
                  "detail": "Resource 2819c223-7f76-453a-919d-413861904646 not found."
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/scim+json": {
              "schema": {
                "$ref": "#/components/schemas/ScimUser"
              },
              "example": {
                "schemas": [
                  "urn:ietf:params:scim:schemas:core:2.0:User"
                ],
                "id": "2819c223-7f76-453a-919d-413861904646",
                "userName": "bjensen",
                "name": {
                  "familyName": "Jensen",
                  "givenName": "Barbara"
                },
                "displayName": "Babs Jensen",
                "emails": [
                  {
                    "value": "bjensen@newdomain.example.com",
                    "type": "work",
                    "primary": true
                  }
                ],
                "active": true
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Patch user",
        "description": "Applies a partial update to a SCIM User resource using PatchOp\n(RFC 7644 §3.5.2). Supported operations: `add`, `remove`, `replace`.\nThe `path` attribute uses SCIM attribute notation including filter\nexpressions (e.g. `emails[type eq \"work\"].value`).\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "User patched.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimUser"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:schemas:core:2.0:User"
                  ],
                  "id": "2819c223-7f76-453a-919d-413861904646",
                  "userName": "bjensen",
                  "active": false
                }
              }
            }
          },
          "400": {
            "description": "Request body is missing or malformed.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "User not found.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/scim+json": {
              "schema": {
                "$ref": "#/components/schemas/ScimPatchOp"
              },
              "example": {
                "schemas": [
                  "urn:ietf:params:scim:api:messages:2.0:PatchOp"
                ],
                "Operations": [
                  {
                    "op": "replace",
                    "path": "active",
                    "value": false
                  },
                  {
                    "op": "replace",
                    "path": "emails[type eq \"work\"].value",
                    "value": "bjensen@newdomain.example.com"
                  }
                ]
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete user",
        "description": "Permanently removes a SCIM User resource. Returns 204 No Content on\nsuccess; the response body is empty.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "204": {
            "description": "User deleted."
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "User not found.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "404",
                  "detail": "Resource 2819c223-7f76-453a-919d-413861904646 not found."
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ]
      }
    },
    "${prefix}/Groups": {
      "get": {
        "summary": "List groups",
        "description": "Returns a paginated list of SCIM Group resources matching the optional\nfilter. Pagination is controlled by `startIndex` (1-based) and `count`.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "in": "query",
            "name": "filter",
            "schema": {
              "type": "string"
            },
            "description": "SCIM filter expression (RFC 7644 §3.4.2.2), e.g.\n`displayName eq \"Tour Guides\"`.\n",
            "example": "displayName eq \"Tour Guides\""
          },
          {
            "in": "query",
            "name": "startIndex",
            "schema": {
              "type": "integer",
              "default": 1
            },
            "description": "1-based index of the first result to return.",
            "example": 1
          },
          {
            "in": "query",
            "name": "count",
            "schema": {
              "type": "integer"
            },
            "description": "Maximum number of results to return per page.",
            "example": 10
          },
          {
            "in": "query",
            "name": "attributes",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated SCIM attribute names to include.",
            "example": "displayName,members"
          },
          {
            "in": "query",
            "name": "excludedAttributes",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated SCIM attribute names to exclude."
          },
          {
            "in": "query",
            "name": "sortBy",
            "schema": {
              "type": "string"
            },
            "description": "Attribute to sort by (parsed but not guaranteed server-side)."
          },
          {
            "in": "query",
            "name": "sortOrder",
            "schema": {
              "type": "string",
              "enum": [
                "ascending",
                "descending"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Group list.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimListResponse"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:ListResponse"
                  ],
                  "totalResults": 2,
                  "startIndex": 1,
                  "itemsPerPage": 2,
                  "Resources": [
                    {
                      "schemas": [
                        "urn:ietf:params:scim:schemas:core:2.0:Group"
                      ],
                      "id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
                      "displayName": "Tour Guides",
                      "members": [
                        {
                          "value": "2819c223-7f76-453a-919d-413861904646",
                          "display": "Babs Jensen"
                        }
                      ]
                    },
                    {
                      "schemas": [
                        "urn:ietf:params:scim:schemas:core:2.0:Group"
                      ],
                      "id": "fc348aa8-3835-40eb-a20b-c726e15c55b5",
                      "displayName": "Employees",
                      "members": []
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid filter expression.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ]
      },
      "post": {
        "summary": "Create group",
        "description": "Creates a new SCIM Group resource. The `displayName` attribute is\nrequired. Returns the created resource with status 201 and a\n`Location` header.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "201": {
            "description": "Group created.",
            "headers": {
              "Location": {
                "description": "URI of the created resource (RFC 7644 section 3.1). Absolute\nwhen `--scim-base-url` is set, prefix-relative otherwise.\n",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimGroup"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:schemas:core:2.0:Group"
                  ],
                  "id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
                  "displayName": "Tour Guides",
                  "members": [
                    {
                      "value": "2819c223-7f76-453a-919d-413861904646",
                      "display": "Babs Jensen"
                    }
                  ],
                  "meta": {
                    "resourceType": "Group",
                    "created": "2024-01-10T09:00:00Z",
                    "location": "https://example.com/scim/v2/Groups/e9e30dba-f08f-4109-8486-d5c6a331660a"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request body is missing or malformed.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "A group with that displayName already exists.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "409",
                  "scimType": "uniqueness",
                  "detail": "Group Tour Guides already exists"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/scim+json": {
              "schema": {
                "$ref": "#/components/schemas/ScimGroup"
              },
              "example": {
                "schemas": [
                  "urn:ietf:params:scim:schemas:core:2.0:Group"
                ],
                "displayName": "Tour Guides",
                "members": [
                  {
                    "value": "2819c223-7f76-453a-919d-413861904646",
                    "display": "Babs Jensen"
                  }
                ]
              }
            }
          }
        }
      }
    },
    "${prefix}/Groups/{id}": {
      "get": {
        "summary": "Get group by ID",
        "description": "Returns a single SCIM Group resource by its server-assigned ID.",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "in": "query",
            "name": "attributes",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated attribute names to return, in place of the default\nset (RFC 7644 section 3.9). Mutually exclusive with\n`excludedAttributes`.\n",
            "example": "displayName"
          },
          {
            "in": "query",
            "name": "excludedAttributes",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated attribute names to omit from the default set.",
            "example": "members"
          }
        ],
        "responses": {
          "200": {
            "description": "Group resource.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimGroup"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:schemas:core:2.0:Group"
                  ],
                  "id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
                  "displayName": "Tour Guides",
                  "members": [
                    {
                      "value": "2819c223-7f76-453a-919d-413861904646",
                      "display": "Babs Jensen"
                    },
                    {
                      "value": "902c246b-6245-4190-8e05-00816be7344a",
                      "display": "Mandy Pepperidge"
                    }
                  ],
                  "meta": {
                    "resourceType": "Group",
                    "created": "2024-01-10T09:00:00Z",
                    "lastModified": "2024-01-15T12:30:00Z",
                    "location": "https://example.com/scim/v2/Groups/e9e30dba-f08f-4109-8486-d5c6a331660a"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Group not found.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "404",
                  "detail": "Resource e9e30dba-f08f-4109-8486-d5c6a331660a not found."
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ]
      },
      "put": {
        "summary": "Replace group",
        "description": "Replaces all attributes of an existing SCIM Group resource with the\nvalues in the request body (full replacement, RFC 7644 §3.5.1).\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Group replaced.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimGroup"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:schemas:core:2.0:Group"
                  ],
                  "id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
                  "displayName": "Tour Guides",
                  "members": [
                    {
                      "value": "2819c223-7f76-453a-919d-413861904646",
                      "display": "Babs Jensen"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Request body is missing or malformed.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Group not found.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/scim+json": {
              "schema": {
                "$ref": "#/components/schemas/ScimGroup"
              },
              "example": {
                "schemas": [
                  "urn:ietf:params:scim:schemas:core:2.0:Group"
                ],
                "id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
                "displayName": "Tour Guides",
                "members": [
                  {
                    "value": "2819c223-7f76-453a-919d-413861904646",
                    "display": "Babs Jensen"
                  },
                  {
                    "value": "902c246b-6245-4190-8e05-00816be7344a",
                    "display": "Mandy Pepperidge"
                  }
                ]
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Patch group",
        "description": "Applies a partial update to a SCIM Group resource using PatchOp\n(RFC 7644 §3.5.2). Typical use-cases: adding or removing members,\nrenaming the group.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Group patched.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimGroup"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:schemas:core:2.0:Group"
                  ],
                  "id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
                  "displayName": "Tour Guides",
                  "members": [
                    {
                      "value": "2819c223-7f76-453a-919d-413861904646",
                      "display": "Babs Jensen"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Request body is missing or malformed.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Group not found.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/scim+json": {
              "schema": {
                "$ref": "#/components/schemas/ScimPatchOp"
              },
              "example": {
                "schemas": [
                  "urn:ietf:params:scim:api:messages:2.0:PatchOp"
                ],
                "Operations": [
                  {
                    "op": "add",
                    "path": "members",
                    "value": [
                      {
                        "value": "2819c223-7f76-453a-919d-413861904646",
                        "display": "Babs Jensen"
                      }
                    ]
                  },
                  {
                    "op": "remove",
                    "path": "members[value eq \"902c246b-6245-4190-8e05-00816be7344a\"]"
                  }
                ]
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete group",
        "description": "Permanently removes a SCIM Group resource. Returns 204 No Content on\nsuccess; the response body is empty.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "204": {
            "description": "Group deleted."
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Group not found.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "404",
                  "detail": "Resource e9e30dba-f08f-4109-8486-d5c6a331660a not found."
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ]
      }
    },
    "${prefix}/Bulk": {
      "post": {
        "summary": "Execute bulk operations",
        "description": "Executes multiple SCIM operations in a single HTTP request (RFC 7644\n§3.7). Operations are processed sequentially. Supports `bulkId`\ncross-references: a POST operation with `\"bulkId\":\"abc\"` creates a\nresource; subsequent operations in the same request can reference it\nas `\"bulkId:abc\"` wherever a SCIM id is expected.\n\nThe `failOnErrors` field controls how many errors to tolerate before\naborting. Set to 0 (default) to process all operations regardless of\nindividual errors.\n\nMaximum payload size and operation count are configurable server-side\n(defaults: 1 MiB / 100 operations). Exceeding either returns 413.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Bulk operations completed (may include per-operation errors).",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimBulkResponse"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:BulkResponse"
                  ],
                  "Operations": [
                    {
                      "method": "POST",
                      "bulkId": "qwerty",
                      "location": "https://example.com/scim/v2/Users/2819c223-7f76-453a-919d-413861904646",
                      "status": "201"
                    },
                    {
                      "method": "DELETE",
                      "status": "204"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Request body is missing, malformed, or exceeds operation count limit.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "413": {
            "description": "Payload exceeds maximum allowed size.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "413",
                  "scimType": "invalidValue",
                  "detail": "Bulk payload too large: 2097152 > 1048576"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/scim+json": {
              "schema": {
                "$ref": "#/components/schemas/ScimBulkRequest"
              },
              "example": {
                "schemas": [
                  "urn:ietf:params:scim:api:messages:2.0:BulkRequest"
                ],
                "failOnErrors": 1,
                "Operations": [
                  {
                    "method": "POST",
                    "bulkId": "qwerty",
                    "path": "/Users",
                    "data": {
                      "schemas": [
                        "urn:ietf:params:scim:schemas:core:2.0:User"
                      ],
                      "userName": "bjensen",
                      "name": {
                        "familyName": "Jensen",
                        "givenName": "Barbara"
                      }
                    }
                  },
                  {
                    "method": "DELETE",
                    "path": "/Users/2819c223-7f76-453a-919d-413861904646"
                  }
                ]
              }
            }
          }
        }
      }
    },
    "${prefix}/ServiceProviderConfig": {
      "get": {
        "summary": "Get service provider configuration",
        "description": "Returns the SCIM 2.0 ServiceProviderConfig document (RFC 7643 §5).\nDescribes which optional features this server supports: PATCH, Bulk,\nfiltering, sorting, ETags, and the active authentication schemes.\nNo authentication is required for this endpoint.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Service provider configuration.",
            "content": {
              "application/scim+json": {
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"
                  ],
                  "patch": {
                    "supported": true
                  },
                  "bulk": {
                    "supported": true,
                    "maxOperations": 100,
                    "maxPayloadSize": 1048576
                  },
                  "filter": {
                    "supported": true,
                    "maxResults": 200
                  },
                  "changePassword": {
                    "supported": false
                  },
                  "sort": {
                    "supported": false
                  },
                  "etag": {
                    "supported": false
                  },
                  "authenticationSchemes": [
                    {
                      "name": "OAuth Bearer Token",
                      "description": "Bearer token. Send \"Authorization: Bearer <token>\".",
                      "type": "oauthbearertoken",
                      "primary": true
                    }
                  ],
                  "meta": {
                    "resourceType": "ServiceProviderConfig",
                    "location": "https://example.com/scim/v2/ServiceProviderConfig"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ]
      }
    },
    "${prefix}/ResourceTypes": {
      "get": {
        "summary": "List resource types",
        "description": "Returns all registered SCIM ResourceType definitions wrapped in a\nListResponse envelope (RFC 7643 §6). This server exposes `User` and\n`Group` resource types.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Resource types list.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimListResponse"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:ListResponse"
                  ],
                  "totalResults": 2,
                  "startIndex": 1,
                  "itemsPerPage": 2,
                  "Resources": [
                    {
                      "schemas": [
                        "urn:ietf:params:scim:schemas:core:2.0:ResourceType"
                      ],
                      "id": "User",
                      "name": "User",
                      "endpoint": "/Users",
                      "description": "User Account",
                      "schema": "urn:ietf:params:scim:schemas:core:2.0:User",
                      "meta": {
                        "resourceType": "ResourceType",
                        "location": "https://example.com/scim/v2/ResourceTypes/User"
                      }
                    },
                    {
                      "schemas": [
                        "urn:ietf:params:scim:schemas:core:2.0:ResourceType"
                      ],
                      "id": "Group",
                      "name": "Group",
                      "endpoint": "/Groups",
                      "description": "Group",
                      "schema": "urn:ietf:params:scim:schemas:core:2.0:Group",
                      "meta": {
                        "resourceType": "ResourceType",
                        "location": "https://example.com/scim/v2/ResourceTypes/Group"
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ]
      }
    },
    "${prefix}/ResourceTypes/{name}": {
      "get": {
        "summary": "Get resource type by name",
        "description": "Returns a single SCIM ResourceType definition by its `id` (name).\nSupported values: `User`, `Group`.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "name parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Resource type definition.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimResourceType"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:schemas:core:2.0:ResourceType"
                  ],
                  "id": "User",
                  "name": "User",
                  "endpoint": "/Users",
                  "description": "User Account",
                  "schema": "urn:ietf:params:scim:schemas:core:2.0:User",
                  "meta": {
                    "resourceType": "ResourceType",
                    "location": "https://example.com/scim/v2/ResourceTypes/User"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "ResourceType not found.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "404",
                  "detail": "ResourceType not found"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ]
      }
    },
    "${prefix}/Schemas": {
      "get": {
        "summary": "List schemas",
        "description": "Returns all SCIM Schema definitions wrapped in a ListResponse envelope\n(RFC 7643 §7). Schemas describe the attributes available on User and\nGroup resources. Schema files are loaded from the server's configured\n`schemas_path` (defaults to `static/schemas/scim/`).\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Schema list.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimListResponse"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:ListResponse"
                  ],
                  "totalResults": 2,
                  "startIndex": 1,
                  "itemsPerPage": 2,
                  "Resources": [
                    {
                      "schemas": [
                        "urn:ietf:params:scim:schemas:core:2.0:Schema"
                      ],
                      "id": "urn:ietf:params:scim:schemas:core:2.0:User",
                      "name": "User",
                      "description": "User Account",
                      "attributes": [],
                      "meta": {
                        "resourceType": "Schema",
                        "location": "https://example.com/scim/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:User"
                      }
                    },
                    {
                      "schemas": [
                        "urn:ietf:params:scim:schemas:core:2.0:Schema"
                      ],
                      "id": "urn:ietf:params:scim:schemas:core:2.0:Group",
                      "name": "Group",
                      "description": "Group",
                      "attributes": [],
                      "meta": {
                        "resourceType": "Schema",
                        "location": "https://example.com/scim/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:Group"
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ]
      }
    },
    "${prefix}/Schemas/{id}": {
      "get": {
        "summary": "Get schema by ID",
        "description": "Returns a single SCIM Schema definition by its URN identifier.\nThe `:id` segment must be URL-encoded (the generator extracts it as\na path parameter automatically). Example URNs:\n- `urn:ietf:params:scim:schemas:core:2.0:User`\n- `urn:ietf:params:scim:schemas:core:2.0:Group`\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Schema definition.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimSchema"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:schemas:core:2.0:Schema"
                  ],
                  "id": "urn:ietf:params:scim:schemas:core:2.0:User",
                  "name": "User",
                  "description": "User Account",
                  "attributes": [
                    {
                      "name": "userName",
                      "type": "string",
                      "multiValued": false,
                      "required": true,
                      "caseExact": false,
                      "mutability": "readWrite",
                      "returned": "default",
                      "uniqueness": "server"
                    }
                  ],
                  "meta": {
                    "resourceType": "Schema",
                    "location": "https://example.com/scim/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:User"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Schema not found.",
            "content": {
              "application/scim+json": {
                "schema": {
                  "$ref": "#/components/schemas/ScimError"
                },
                "example": {
                  "schemas": [
                    "urn:ietf:params:scim:api:messages:2.0:Error"
                  ],
                  "status": "404",
                  "detail": "Schema not found"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "SCIM 2.0"
        ]
      }
    },
    "${prefix}/schemas/{name}": {
      "get": {
        "summary": "Get JSON schema by name",
        "description": "Returns a JSON schema file from the top-level `schemas/` directory\nof the configured static path.  The `:name` segment must be made of\nalphanumerics and hyphens, in parts joined by single dots, with a\n`.json` extension; a name without that extension is served as a\nplain static file.\n\nSchema files may contain `__FOO_BAR__` placeholders that are\nsubstituted at serve-time with the value of the corresponding\n`--foo-bar` server option, allowing the same file to be used for\nboth server-side validation and browser-side form rendering. A\nschema that `extends` another is served merged with it, and a\nschema file of the configuration is served at the `schemaUrl` the\nconfiguration API gives for it, wherever the file is.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "name parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "JSON schema file.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LdapSchema"
                },
                "example": {
                  "entity": {
                    "name": "standardUser",
                    "mainAttribute": "uid",
                    "objectClass": [
                      "top",
                      "inetOrgPerson"
                    ],
                    "base": "ou=users,dc=example,dc=com"
                  },
                  "strict": true,
                  "attributes": {
                    "uid": {
                      "type": "string",
                      "required": true,
                      "role": "identifier"
                    },
                    "cn": {
                      "type": "string",
                      "required": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid schema name."
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Schema file not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "A schema it extends is missing or invalid."
          }
        },
        "tags": [
          "Static Files"
        ]
      }
    },
    "${prefix}/schemas/{dir}/{name}": {
      "get": {
        "summary": "Get JSON schema from subdirectory",
        "description": "Returns a JSON schema from a named sub-directory of `schemas/`.\n`:dir` and `:name` follow the rules of the top-level route, and are\nvalidated before the file-system path is resolved, and the\nresolved path is checked against the schemas root to prevent\npath-traversal attacks.\n\nAvailable sub-directories depend on the static path configured at\nstartup.  Typical deployments include `standard`, `twake`, `ad`,\n`scim`, and `obm`.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "name": "dir",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "dir parameter"
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "name parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "JSON schema file from the sub-directory.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LdapSchema"
                },
                "example": {
                  "entity": {
                    "name": "twakeUser",
                    "mainAttribute": "uid",
                    "objectClass": [
                      "top",
                      "inetOrgPerson",
                      "twakePerson"
                    ],
                    "base": "ou=users,dc=example,dc=com"
                  },
                  "strict": false,
                  "attributes": {
                    "uid": {
                      "type": "string",
                      "required": true,
                      "role": "identifier"
                    },
                    "mail": {
                      "type": "string",
                      "required": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid directory or schema name."
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Schema file not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "A schema it extends is missing or invalid."
          }
        },
        "tags": [
          "Static Files"
        ]
      }
    },
    "/static/schemas/{dir}/{subdir}/{name}": {
      "get": {
        "summary": "Get JSON schema from a nested subdirectory",
        "description": "Returns a JSON schema from a sub-directory of `schemas/` at any\ndepth, such as `/static/schemas/twake/nomenclature/twakeTitle.json`,\nwith the rules of the other schema routes for every segment.\n",
        "parameters": [
          {
            "name": "prefix",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "prefix parameter"
          },
          {
            "name": "dir",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "subdir",
            "in": "path",
            "required": true,
            "description": "One or more directories, separated by slashes.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "JSON schema file from the sub-directory.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LdapSchema"
                }
              }
            }
          },
          "400": {
            "description": "Invalid directory or schema name."
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Schema file not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "A schema it extends is missing or invalid."
          }
        },
        "tags": [
          "Static Files"
        ]
      }
    },
    "/api/v1/users/{user}/app-accounts": {
      "get": {
        "summary": "List app accounts for a user",
        "description": "Returns all application-specific accounts belonging to `:user`, where\n`:user` is the principal account email (globally unique). Ownership is\nresolved by the mail attribute, not the LDAP `uid`, which may repeat\nunder different subtrees of the directory. The principal account\n(whose uid equals the mail address) is always filtered out.\n\nResults are sorted alphabetically by `uid`.\n",
        "parameters": [
          {
            "name": "user",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "user parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "List of app accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AppAccount"
                  }
                },
                "example": [
                  {
                    "uid": "alice_c04729183",
                    "name": "Work laptop"
                  },
                  {
                    "uid": "alice_c09812345",
                    "name": "Mobile phone"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "User not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "App Accounts"
        ]
      },
      "post": {
        "summary": "Create an app account for a user",
        "description": "Creates a new application-specific identity under the configured\napplicative-account LDAP branch.  The server generates a random\nuid (`<prefix>_c<8-digits>`, prefix derived from `:user`) and a\ncryptographically secure password, then:\n\n1. Copies `cn`, `sn`, `givenName`, `mail`, and `displayName` from\n   the principal user entry.\n2. Adds the cleartext password to the principal account\n   (`uid=<mail>`) so single-point authentication still works.\n\nStep 2 is not optional: services bind against the principal account,\nso a password absent there would authenticate nowhere. If it fails,\nthe app account created in step 1 is rolled back and the call returns\n`500` rather than a credential that does not work.\n\nReturns a `400` if the user has already reached the server-side\nmaximum number of app accounts (`max_app_accounts`, default 5).\n\n**The generated password is returned only once** — store it\nimmediately.\n",
        "parameters": [
          {
            "name": "user",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "user parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "App account created — includes the one-time password.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppAccountCreated"
                },
                "example": {
                  "uid": "alice_c04729183",
                  "pwd": "Ab3@-xYz!-9pQ#-Sv4$-mN8!-pQ5@",
                  "mail": "alice@example.com"
                }
              }
            }
          },
          "400": {
            "description": "User has no mail attribute or maximum account limit reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "User not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "App Accounts"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppAccountCreate"
              },
              "example": {
                "name": "Work laptop"
              }
            }
          }
        }
      }
    },
    "/api/v1/users/{user}/app-accounts/{uid}": {
      "delete": {
        "summary": "Delete an app account",
        "description": "Deletes the app account identified by `:uid` and removes its\npassword from the principal account (`uid=<mail>`). `:user` is the\nprincipal account email.\n\nThe target account's mail must match `:user` — attempting to delete an\naccount that belongs to a different principal returns `403`.\n\nThe operation is **idempotent**: if the account does not exist,\nthe response is still `200` with the `uid` echoed back.\n\nRevocation requires reading the account's `userPassword` to know which\nof the principal's values to remove. If it cannot be read — typically\na bind DN without read access on `userPassword` in the applicative\nbranch — or if removing it fails, the account is **kept** and the call\nreturns `500`, so that the request stays replayable once the cause is\nfixed. It never reports a revocation that did not happen.\n",
        "parameters": [
          {
            "name": "user",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "user parameter"
          },
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "uid parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "App account deleted (or was already absent).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "uid": {
                      "type": "string",
                      "description": "UID of the deleted account."
                    }
                  }
                },
                "example": {
                  "uid": "alice_c04729183"
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "The `:uid` does not belong to `:user`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "The account password could not be read or could not be removed\nfrom the principal account; the app account was left in place.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "tags": [
          "App Accounts"
        ]
      }
    },
    "/api/v1/organizations/{id}/groups": {
      "get": {
        "summary": "Get groups",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeGroups"
        ]
      },
      "post": {
        "summary": "Create groups",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeGroups"
        ]
      }
    },
    "/api/v1/organizations/{id}/groups/{groupId}": {
      "get": {
        "summary": "Get groups",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "groupId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeGroups"
        ]
      },
      "patch": {
        "summary": "Modify groups",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "groupId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeGroups"
        ]
      },
      "delete": {
        "summary": "Delete groups",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "groupId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeGroups"
        ]
      }
    },
    "/api/v1/organizations/{id}/groups/{groupId}/members": {
      "get": {
        "summary": "Get members",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "groupId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeGroups"
        ]
      },
      "post": {
        "summary": "Create members",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "groupId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeGroups"
        ]
      }
    },
    "/api/v1/organizations/{id}/groups/{groupId}/members/{userId}": {
      "delete": {
        "summary": "Delete members",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "groupId parameter"
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "userId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeGroups"
        ]
      }
    },
    "/api/v1/twake/instances/ensure": {
      "post": {
        "summary": "Make sure an account has its workplace instance",
        "description": "Writes a missing address and announces the account when its instance\nexists, and asks for the instance when it does not. Answers `ready`\nonce the address is written, `pending` while the instance is built.\nThe caller reads and writes the account with its own rights.\n",
        "responses": {
          "200": {
            "description": "Done or under way.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "state"
                  ],
                  "properties": {
                    "state": {
                      "anyOf": [
                        {
                          "const": "ready",
                          "type": "string"
                        },
                        {
                          "const": "pending",
                          "type": "string"
                        }
                      ]
                    }
                  }
                },
                "example": {
                  "state": "ready"
                }
              }
            }
          },
          "400": {
            "description": "Neither a DN nor a mail, or a malformed one."
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "No such account the caller can read."
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Twake"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The account, by DN or by mail.",
                "properties": {
                  "dn": {
                    "type": "string"
                  },
                  "mail": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/users/{user}/quota-usage": {
      "get": {
        "summary": "Get user quota usage",
        "description": "Returns the current mailbox quota counters (message count and\nstorage size) for the given user. Requires the Apache James\nintegration to be configured (`james_webadmin_url`,\n`james_webadmin_token`). The quota data is fetched live from\nthe James WebAdmin API.\n",
        "parameters": [
          {
            "name": "user",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "user parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Quota information retrieved successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JamesQuotaUsage"
                },
                "example": {
                  "count": {
                    "used": 142,
                    "max": 10000
                  },
                  "size": {
                    "used": 524288000,
                    "max": 2147483648
                  }
                }
              }
            }
          },
          "400": {
            "description": "User has no mail attribute configured in LDAP.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "User alice has no mail attribute"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "User not found in LDAP, or quota entry absent in James.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "User alice not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "James WebAdmin API is unreachable or returned an error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Failed to retrieve quota from James: Bad Gateway"
                }
              }
            }
          }
        },
        "tags": [
          "Apache James Integration"
        ]
      }
    },
    "/api/v1/users/{user}/mail-migration": {
      "get": {
        "summary": "Where the migration proxy routes a user",
        "description": "Asks the Twake Mail migration proxy whether the user is on its\nmigrated list, that is routed to the new backend. The user is\nfound by `uid` across the directory and answered only when the\ncaller can read the entry.\n",
        "parameters": [
          {
            "name": "user",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "user parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "The answer of the proxy.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MailMigrationStatus"
                },
                "example": {
                  "migrated": true
                }
              }
            }
          },
          "400": {
            "description": "User has no mail attribute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "User not found, or not readable by the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          },
          "502": {
            "description": "The proxy is unreachable or answered an error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "tags": [
          "Apache James Integration"
        ]
      }
    },
    "/api/v1/organizations/{id}/spaces": {
      "get": {
        "summary": "Get spaces",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeSpaces"
        ]
      },
      "post": {
        "summary": "Create spaces",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeSpaces"
        ]
      }
    },
    "/api/v1/organizations/{id}/spaces/{spaceId}": {
      "get": {
        "summary": "Get spaces",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "spaceId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "spaceId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeSpaces"
        ]
      },
      "patch": {
        "summary": "Modify spaces",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "spaceId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "spaceId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeSpaces"
        ]
      },
      "delete": {
        "summary": "Delete spaces",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "spaceId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "spaceId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeSpaces"
        ]
      }
    },
    "/api/v1/organizations/{id}/spaces/{spaceId}/members": {
      "get": {
        "summary": "Get members",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "spaceId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "spaceId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeSpaces"
        ]
      },
      "post": {
        "summary": "Create members",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "spaceId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "spaceId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeSpaces"
        ]
      }
    },
    "/api/v1/organizations/{id}/spaces/{spaceId}/members/{userId}": {
      "patch": {
        "summary": "Modify members",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "spaceId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "spaceId parameter"
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "userId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeSpaces"
        ]
      },
      "delete": {
        "summary": "Delete members",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "spaceId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "spaceId parameter"
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "userId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeSpaces"
        ]
      }
    },
    "/api/v1/organizations/{id}/spaces/{spaceId}/groups": {
      "get": {
        "summary": "Get groups",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "spaceId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "spaceId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeSpaces"
        ]
      },
      "post": {
        "summary": "Create groups",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "spaceId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "spaceId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeSpaces"
        ]
      }
    },
    "/api/v1/organizations/{id}/spaces/{spaceId}/groups/{groupId}": {
      "patch": {
        "summary": "Modify groups",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "spaceId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "spaceId parameter"
          },
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "groupId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeSpaces"
        ]
      },
      "delete": {
        "summary": "Delete groups",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "name": "spaceId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "spaceId parameter"
          },
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "groupId parameter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "twakeSpaces"
        ]
      }
    },
    "/api/v1/twake/tombstones/erase": {
      "post": {
        "summary": "Erase a tombstone",
        "description": "Removes a deleted account for good, with its group memberships. The\ndeletion must be older than `--twake-tombstone-erase-min-age`, unless\n`force` is true. Nothing is published.\n",
        "responses": {
          "200": {
            "description": "Erased.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "No tombstone at this DN."
          },
          "409": {
            "description": "The deletion is too recent and `force` is not set."
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Twake"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "dn"
                ],
                "properties": {
                  "dn": {
                    "type": "string"
                  },
                  "force": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/{resource}": {
      "get": {
        "summary": "List entries",
        "description": "Returns all entries in the flat LDAP branch, keyed by their\n`mainAttribute` value (e.g. `uid` for users). The optional\n`match` and `attribute` query parameters filter results using\na substring LDAP search (`attribute=*match*`). `attribute` accepts\nseveral names separated by commas, and the clauses are then joined\nwith `|`. The `attributes`\nparameter limits which LDAP attributes are returned.\n\nWithout `limit`, the answer holds every match or fails: a directory\nwhose size limit is below the number of matches makes it a `422`.\nWith `limit`, at most that many entries come back, and the\n`X-Result-Truncated: true` header says others were left out.\n",
        "parameters": [
          {
            "in": "path",
            "name": "resource",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Plural name of the flat resource (e.g. `users`, `mailgroups`).\nEach concrete plugin sets its own value.\n",
            "example": "users"
          },
          {
            "in": "query",
            "name": "match",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Substring to match. Must be used together with `attribute`.\nThe resulting LDAP filter is `(attribute=*match*)`, or\n`(|(a=*match*)(b=*match*))` when several attributes are named.\n",
            "example": "alice"
          },
          {
            "in": "query",
            "name": "attribute",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "LDAP attribute name to match against (used with `match`).\nSeveral may be given, separated by commas; an entry matching any\nof them is returned. Each name must be indexed for a substring\nsearch, or the directory scans the branch.\n",
            "example": "cn"
          },
          {
            "in": "query",
            "name": "attributes",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated list of LDAP attributes to include in each\nreturned entry. Omit to return all attributes. The\n`mainAttribute` is always returned: it keys the map.\n",
            "example": "uid,cn,mail"
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Return at most this many entries. When more match, the response\ncarries `X-Result-Truncated: true`. It also does when the\ndirectory refused to list every match, even if fewer than\n`limit` came back: the directory does not say whether it cut\nthat answer short.\n",
            "example": 50
          }
        ],
        "responses": {
          "200": {
            "description": "Map of entries keyed by mainAttribute value.",
            "headers": {
              "X-Result-Truncated": {
                "description": "`true` when `limit` was given and the answer may not hold\nevery match (see `limit`). Absent otherwise.\n",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlatList"
                },
                "example": {
                  "alice": {
                    "dn": "uid=alice,ou=users,dc=example,dc=com",
                    "uid": "alice",
                    "cn": "Alice Smith",
                    "mail": "alice@example.com"
                  },
                  "bob": {
                    "dn": "uid=bob,ou=users,dc=example,dc=com",
                    "uid": "bob",
                    "cn": "Bob Jones",
                    "mail": "bob@example.com"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid LDAP attribute name in `attribute` parameter, or `limit`\nis not a positive integer.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "422": {
            "description": "No `limit` was given and more entries match than the directory\nwill list in one answer (LDAP `sizeLimitExceeded`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Too many entries to list at once: the directory's size limit was exceeded. Narrow the search, or pass `limit`."
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Entities"
        ]
      },
      "post": {
        "summary": "Create entry",
        "description": "Creates a new entry in the flat LDAP branch. The `mainAttribute`\nfield (e.g. `uid`) is required and used to construct the DN.\nReturns the newly created entry on success (HTTP 201).\n",
        "parameters": [
          {
            "in": "path",
            "name": "resource",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Plural name of the flat resource (e.g. `users`, `mailgroups`).\nEach concrete plugin sets its own value.\n",
            "example": "users"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response"
          },
          "201": {
            "description": "Entry created; returns the new entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlatEntry"
                },
                "example": {
                  "dn": "uid=carol,ou=users,dc=example,dc=com",
                  "uid": "carol",
                  "cn": "Carol White",
                  "sn": "White",
                  "mail": "carol@example.com"
                }
              }
            }
          },
          "400": {
            "description": "Missing required field or validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Entry already exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Entities"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlatCreate"
              },
              "example": {
                "uid": "carol",
                "cn": "Carol White",
                "sn": "White",
                "mail": "carol@example.com"
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/{resource}/{id}": {
      "get": {
        "summary": "Get entry by ID or DN",
        "description": "Returns a single entry identified by its `mainAttribute` value\n(e.g. a `uid`) or by its full DN. If the value starts with\n`mainAttribute=` it is treated as a DN; otherwise it is treated\nas a raw RDN value.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "in": "path",
            "name": "resource",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Plural name of the flat resource (e.g. `users`, `mailgroups`).\nEach concrete plugin sets its own value.\n",
            "example": "users"
          }
        ],
        "responses": {
          "200": {
            "description": "The requested entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlatEntry"
                },
                "example": {
                  "dn": "uid=alice,ou=users,dc=example,dc=com",
                  "uid": "alice",
                  "cn": "Alice Smith",
                  "sn": "Smith",
                  "mail": "alice@example.com"
                }
              }
            }
          },
          "400": {
            "description": "The supplied DN is not a direct child of the configured base.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Entry not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "user not found"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Entities"
        ]
      },
      "delete": {
        "summary": "Delete entry",
        "description": "Permanently removes the entry identified by `id` from the LDAP\nbranch. The `id` may be either a `mainAttribute` value or the\nentry's full DN.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "in": "path",
            "name": "resource",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Plural name of the flat resource (e.g. `users`, `mailgroups`).\nEach concrete plugin sets its own value.\n",
            "example": "users"
          }
        ],
        "responses": {
          "200": {
            "description": "Entry deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Entry not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Entities"
        ]
      },
      "put": {
        "summary": "Modify entry",
        "description": "Applies a partial update to the entry identified by `id`. The\nbody follows the LDAP modify structure: `add`, `replace`, and/or\n`delete` maps. The `mainAttribute` (e.g. `uid`) cannot be\nchanged through this endpoint.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "in": "path",
            "name": "resource",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Plural name of the flat resource (e.g. `users`, `mailgroups`).\nEach concrete plugin sets its own value.\n",
            "example": "users"
          }
        ],
        "responses": {
          "200": {
            "description": "Entry updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "400": {
            "description": "Validation error or attempt to modify a fixed attribute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Entry not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Entities"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlatModify"
              },
              "example": {
                "replace": {
                  "mail": "alice.smith@example.com",
                  "cn": "Alice M. Smith"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/{resource}/{id}/move": {
      "post": {
        "summary": "Move entry to another organisation",
        "description": "Moves the entry to a different organisational unit by updating\nthe configured department-link and department-path attributes\n(`ldap_organization_link_attribute` and\n`ldap_organization_path_attribute`). The plugin's schema must\ndeclare both attributes; otherwise the request is rejected with\n400.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "in": "path",
            "name": "resource",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Plural name of the flat resource (e.g. `users`, `mailgroups`).\nEach concrete plugin sets its own value.\n",
            "example": "users"
          }
        ],
        "responses": {
          "200": {
            "description": "Entry moved; returns the new department path and link.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "departmentPath",
                    "departmentLink"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "departmentPath": {
                      "type": "string"
                    },
                    "departmentLink": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "departmentPath": "/engineering",
                  "departmentLink": "ou=engineering,ou=departments,dc=example,dc=com"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid `targetOrgDn`, or schema does not support\nthe move operation.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Entry or target organisation not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Entities"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlatMoveRequest"
              },
              "example": {
                "targetOrgDn": "ou=engineering,ou=departments,dc=example,dc=com"
              }
            }
          }
        }
      }
    },
    "/api/v1/ldap/{resource}/{id}/rename": {
      "post": {
        "summary": "Rename entry (change its identifier)",
        "description": "Changes the `mainAttribute` value of the entry — its RDN, and so its\nDN — and rewrites the references other entries hold to the old DN:\ngroup memberships and ownerships, delegations, managers, every\npointer a loaded schema declares towards this branch. The rewrite is\nfinished when the call returns.\n\nThe body field is `newId` for every resource, whatever that resource\ncalls its `mainAttribute`, and it carries a value, not a DN.\n\nThe new value is held to the same `test` as a creation would hold it\nto, and refused when the identifier is already taken in this branch.\nA `unique` constraint the schema widens to other branches is **not**\napplied here: it is enforced from the add and modify hooks, while\nwhat a rename needs is the RDN's own branch — which is the namespace\nthe directory itself enforces.\n\nRe-issuing the same rename is a repair, not an error: when the entry\nalready carries the new identifier, the reference rewrite runs alone.\nThat is how a `207` is finished off.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "minLength": 1,
              "type": "string"
            },
            "description": "id parameter"
          },
          {
            "in": "path",
            "name": "resource",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Plural name of the flat resource (e.g. `users`, `mailgroups`).\nEach concrete plugin sets its own value.\n",
            "example": "users"
          }
        ],
        "responses": {
          "200": {
            "description": "Entry renamed and every reference rewritten.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlatRenameResult"
                },
                "example": {
                  "success": true,
                  "dn": "uid=bob,ou=users,dc=example,dc=com",
                  "referencesUpdated": 3
                }
              }
            }
          },
          "207": {
            "description": "Entry renamed, but at least one reference could not be\nrewritten. The rename is done and is not rolled back; re-issue\nthe same request to finish the rewrite.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlatRenameResult"
                },
                "example": {
                  "success": false,
                  "dn": "uid=bob,ou=users,dc=example,dc=com",
                  "referencesUpdated": 1,
                  "referencesFailed": [
                    {
                      "attribute": "member",
                      "count": 2
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid `newId`, a value the schema refuses, or an\nattribute sent beside it.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "No write permission on the branch.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entry not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The identifier is already taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          }
        },
        "tags": [
          "Entities"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlatRenameRequest"
              },
              "example": {
                "newId": "bob"
              }
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Groups",
      "description": "Groups operations"
    },
    {
      "name": "Organizations",
      "description": "Organizations operations"
    },
    {
      "name": "Entities",
      "description": "Entities operations"
    },
    {
      "name": "Bulk Import",
      "description": "Bulk Import operations"
    },
    {
      "name": "Apache James Integration",
      "description": "Apache James Integration operations"
    },
    {
      "name": "Twake Calendar",
      "description": "Twake Calendar operations"
    },
    {
      "name": "Configuration",
      "description": "Configuration operations"
    },
    {
      "name": "Static Files",
      "description": "Static Files operations"
    },
    {
      "name": "Trash",
      "description": "Trash operations"
    },
    {
      "name": "External Users",
      "description": "External Users operations"
    },
    {
      "name": "SCIM 2.0",
      "description": "SCIM 2.0 operations"
    },
    {
      "name": "Password Policy",
      "description": "Password Policy operations"
    },
    {
      "name": "App Accounts",
      "description": "App Accounts operations"
    },
    {
      "name": "Demo",
      "description": "Demo operations"
    },
    {
      "name": "Authorization (Dynamic)",
      "description": "Authorization (Dynamic) operations"
    }
  ],
  "components": {
    "schemas": {
      "Config": {
        "type": "object",
        "description": "Aggregated server configuration. `apiPrefix`, `ldapBase` and\n`features` are always present; `version` is present unless the server\ncould not read it at start-up; `features` is a dynamic map whose keys\nare the names of loaded plugins that declare the `configurable` role.\nEach plugin contributes an arbitrary sub-object — consult the\nindividual plugin documentation for its exact shape.\n",
        "additionalProperties": true,
        "properties": {
          "apiPrefix": {
            "type": "string",
            "description": "The API base path configured on this server.",
            "example": "/api"
          },
          "ldapBase": {
            "type": "string",
            "description": "The root LDAP suffix used by this server.",
            "example": "dc=example,dc=com"
          },
          "version": {
            "type": "string",
            "description": "The server's version, as its package declares it. A client reads it\nto tell an administrator the server is older than it supports; a\nserver that cannot tell its version leaves the field out.\n",
            "example": "1.2.1"
          },
          "features": {
            "type": "object",
            "description": "Per-plugin configuration blocks.  Keys match the plugin's\n`name` field; values are plugin-specific objects.\n",
            "additionalProperties": true,
            "example": {
              "ldapGroups": {
                "enabled": true,
                "base": "ou=groups,dc=example,dc=com",
                "mainAttribute": "cn",
                "objectClass": [
                  "top",
                  "groupOfNames"
                ]
              },
              "static": {
                "enabled": true,
                "staticPath": "/static"
              },
              "appAccountsApi": {
                "enabled": true,
                "base": "ou=app-accounts,dc=example,dc=com",
                "maxAccounts": 5
              }
            }
          }
        },
        "example": {
          "apiPrefix": "/api",
          "ldapBase": "dc=example,dc=com",
          "version": "1.2.1",
          "features": {
            "ldapGroups": {
              "enabled": true,
              "base": "ou=groups,dc=example,dc=com",
              "mainAttribute": "cn"
            },
            "static": {
              "enabled": true,
              "staticPath": "/static"
            }
          }
        }
      },
      "BulkImportResult": {
        "type": "object",
        "description": "Summary returned after a bulk import operation.",
        "required": [
          "success",
          "total",
          "created",
          "updated",
          "skipped",
          "failed",
          "errors",
          "details"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "description": "True when no fatal error aborted the import early."
          },
          "total": {
            "type": "integer",
            "description": "Total number of CSV data rows (excluding header).",
            "example": 42
          },
          "created": {
            "type": "integer",
            "description": "Entries successfully created (or that would be, in a dry run).",
            "example": 38
          },
          "updated": {
            "type": "integer",
            "description": "Existing entries updated (or that would be, in a dry run).",
            "example": 2
          },
          "skipped": {
            "type": "integer",
            "description": "Existing entries skipped because `updateExisting` was false.",
            "example": 1
          },
          "failed": {
            "type": "integer",
            "description": "Rows that caused an error.",
            "example": 1
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "line",
                "error"
              ],
              "properties": {
                "line": {
                  "type": "integer",
                  "description": "1-based CSV line number (2 = first data row)."
                },
                "identifier": {
                  "type": "string",
                  "description": "Value of the main attribute for the failing row (if known)."
                },
                "error": {
                  "type": "string",
                  "description": "Error message."
                },
                "code": {
                  "type": "string",
                  "description": "`unique` or `mailDomain`, with `attribute`, `value` and\n`domains`, as in the `Error` answer of a single write;\n`forbidden` when the caller may not write the row.\n"
                },
                "attribute": {
                  "type": "string"
                },
                "value": {
                  "type": "string"
                },
                "domains": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            },
            "example": [
              {
                "line": 5,
                "identifier": "bob",
                "error": "Missing required attribute: mail"
              }
            ]
          },
          "details": {
            "type": "object",
            "required": [
              "duration",
              "linesProcessed"
            ],
            "properties": {
              "duration": {
                "type": "string",
                "description": "Wall-clock time for the import (e.g. `1.2s`).",
                "example": "1.2s"
              },
              "linesProcessed": {
                "type": "integer",
                "description": "Total data rows attempted.",
                "example": 42
              }
            }
          }
        }
      },
      "Group": {
        "type": "object",
        "description": "An LDAP group entry (groupOfNames / groupOfUniqueNames).",
        "required": [
          "dn",
          "cn"
        ],
        "properties": {
          "dn": {
            "type": "string",
            "description": "Fully-qualified distinguished name of the entry.",
            "example": "cn=admins,ou=groups,dc=example,dc=com"
          },
          "cn": {
            "type": "string",
            "description": "Common name (RDN attribute) of the group.",
            "example": "admins"
          },
          "description": {
            "type": "string",
            "example": "Server administrators"
          },
          "member": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "DNs of the group's members.",
            "example": [
              "uid=alice,ou=users,dc=example,dc=com",
              "uid=bob,ou=users,dc=example,dc=com"
            ]
          }
        }
      },
      "GroupCreate": {
        "type": "object",
        "required": [
          "cn"
        ],
        "properties": {
          "cn": {
            "type": "string",
            "description": "Common name of the new group."
          },
          "description": {
            "type": "string"
          },
          "member": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Initial members (DNs)."
          }
        },
        "example": {
          "cn": "admins",
          "description": "Server administrators",
          "member": [
            "uid=alice,ou=users,dc=example,dc=com"
          ]
        }
      },
      "GroupModify": {
        "type": "object",
        "description": "Partial update. Each provided attribute is replaced wholesale. Use\nthe dedicated `/members` endpoints to add/remove members one at a\ntime without supplying the full list.\n",
        "properties": {
          "description": {
            "type": "string"
          },
          "member": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "example": {
          "description": "Updated description"
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "What was refused, in English."
          },
          "code": {
            "type": "string",
            "description": "For a client to explain the refusal in its own words, when there\nis one: `unique` (a value already used), `mailDomain` (an address\noutside the authorised mail domains), `forbidden` (the caller may\nnot act on this branch). Absent for any other refusal, which only\n`error` describes. The Twake groups API answers its own codes\n(`INVALID_INPUT`, `REFUSED`…).\n"
          },
          "attribute": {
            "type": "string",
            "description": "The attribute refused, for `unique` and `mailDomain`."
          },
          "value": {
            "type": "string",
            "description": "The value refused, for `unique` and `mailDomain`."
          },
          "domains": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The authorised mail domains, for `mailDomain`."
          }
        },
        "example": {
          "error": "entity: \"value\" is already used (attribute must be unique)",
          "code": "unique",
          "attribute": "attribute",
          "value": "value"
        }
      },
      "Organization": {
        "type": "object",
        "description": "An LDAP organizational unit entry.",
        "required": [
          "dn",
          "ou"
        ],
        "properties": {
          "dn": {
            "type": "string",
            "description": "Fully-qualified distinguished name of the entry.",
            "example": "ou=Engineering,ou=organizations,dc=example,dc=com"
          },
          "ou": {
            "type": "string",
            "description": "Organizational unit name (RDN attribute).",
            "example": "Engineering"
          },
          "o": {
            "type": "string",
            "description": "Organization display name.",
            "example": "Engineering Department"
          },
          "l": {
            "type": "string",
            "description": "Locality / city.",
            "example": "Paris"
          },
          "description": {
            "type": "string",
            "example": "Software engineering division"
          },
          "organizationLink": {
            "type": "string",
            "description": "DN of the parent organization (link attribute).",
            "example": "ou=organizations,dc=example,dc=com"
          },
          "organizationPath": {
            "type": "string",
            "description": "Human-readable slash-separated path to this node, used by the\nUI tree. Set automatically on creation and move.\n",
            "example": "Engineering / Backend"
          },
          "member": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "DNs of entries linked to this organization.",
            "example": [
              "uid=alice,ou=users,dc=example,dc=com"
            ]
          }
        }
      },
      "OrgSummary": {
        "type": "object",
        "description": "Lightweight organization reference (used in node lists).",
        "required": [
          "dn",
          "ou"
        ],
        "properties": {
          "dn": {
            "type": "string",
            "example": "ou=HR",
            "ou=organizations": null,
            "dc=example": null,
            "dc=com": null
          },
          "ou": {
            "type": "string",
            "example": "HR"
          },
          "description": {
            "type": "string"
          },
          "objectClass": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "top",
              "organizationalUnit"
            ]
          }
        }
      },
      "OrganizationCreate": {
        "type": "object",
        "required": [
          "ou"
        ],
        "properties": {
          "ou": {
            "type": "string",
            "description": "Name of the new organizational unit.",
            "example": "Marketing"
          },
          "parentDn": {
            "type": "string",
            "description": "DN of the parent organizational unit. Defaults to the configured\ntop organization when omitted.\n",
            "example": "ou=organizations,dc=example,dc=com"
          },
          "description": {
            "type": "string"
          },
          "l": {
            "type": "string"
          }
        },
        "example": {
          "ou": "Marketing",
          "parentDn": "ou=organizations,dc=example,dc=com",
          "description": "Marketing division"
        }
      },
      "OrganizationModify": {
        "type": "object",
        "description": "Partial update. Any provided attribute is replaced wholesale.\n`ou` cannot be changed via this endpoint — use the move endpoint\nto relocate the node within the tree.\n",
        "properties": {
          "description": {
            "type": "string"
          },
          "l": {
            "type": "string"
          },
          "o": {
            "type": "string"
          }
        },
        "example": {
          "description": "Updated description for Marketing"
        }
      },
      "PasswordPolicy": {
        "type": "object",
        "description": "Active OpenLDAP ppolicy configuration. All duration fields are in\n**seconds**. Absent fields mean the policy attribute is not set.\n",
        "properties": {
          "dn": {
            "type": "string",
            "description": "DN of the ppolicy entry.",
            "example": "cn=default,ou=policies,dc=example,dc=com"
          },
          "pwdMaxAge": {
            "type": "integer",
            "description": "Maximum password age in seconds (0 = never expires).",
            "example": 7776000
          },
          "pwdMinAge": {
            "type": "integer",
            "description": "Minimum password age in seconds before it may be changed.",
            "example": 0
          },
          "pwdInHistory": {
            "type": "integer",
            "description": "Number of previous passwords stored.",
            "example": 5
          },
          "pwdCheckQuality": {
            "type": "integer",
            "description": "Password quality enforcement level (0-2).",
            "example": 1
          },
          "pwdMinLength": {
            "type": "integer",
            "description": "Minimum password length.",
            "example": 12
          },
          "pwdMaxFailure": {
            "type": "integer",
            "description": "Maximum bind failures before lockout.",
            "example": 5
          },
          "pwdLockout": {
            "type": "boolean",
            "description": "Whether account lockout is enabled.",
            "example": true
          },
          "pwdLockoutDuration": {
            "type": "integer",
            "description": "Lockout duration in seconds (0 = permanent until admin reset).",
            "example": 300
          },
          "pwdGraceAuthNLimit": {
            "type": "integer",
            "description": "Number of grace logins allowed after password expires.",
            "example": 0
          },
          "pwdExpireWarning": {
            "type": "integer",
            "description": "Seconds before expiry at which a warning is issued.",
            "example": 1209600
          },
          "pwdMustChange": {
            "type": "boolean",
            "description": "If true, users must change their password after an admin reset.",
            "example": true
          },
          "pwdAllowUserChange": {
            "type": "boolean",
            "description": "Whether users are allowed to change their own password.",
            "example": true
          }
        }
      },
      "PasswordStatus": {
        "type": "object",
        "description": "Current password status for a specific user account.",
        "required": [
          "dn",
          "passwordSet",
          "isExpired",
          "isExpiringSoon",
          "mustChange",
          "isLocked",
          "failureCount",
          "graceLoginsUsed"
        ],
        "properties": {
          "dn": {
            "type": "string",
            "description": "Fully-qualified DN of the user.",
            "example": "uid=alice,ou=users,dc=example,dc=com"
          },
          "passwordSet": {
            "type": "boolean",
            "description": "Whether a userPassword attribute is present.",
            "example": true
          },
          "lastChanged": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "ISO-8601 timestamp of last password change, or null if never set.",
            "example": "2026-01-15T10:00:00.000Z"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "ISO-8601 expiry timestamp, or null if no max age is configured.",
            "example": "2026-04-15T10:00:00.000Z"
          },
          "daysUntilExpiration": {
            "type": "integer",
            "nullable": true,
            "description": "Remaining days until expiry (negative means already expired).",
            "example": 80
          },
          "isExpired": {
            "type": "boolean",
            "example": false
          },
          "isExpiringSoon": {
            "type": "boolean",
            "description": "True when expiry is within the configured warning window.",
            "example": false
          },
          "mustChange": {
            "type": "boolean",
            "description": "True when `pwdReset` is set (admin forced change).",
            "example": false
          },
          "isLocked": {
            "type": "boolean",
            "description": "True when the account is currently locked.",
            "example": false
          },
          "lockedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "ISO-8601 timestamp of lockout, or null.",
            "example": null
          },
          "failureCount": {
            "type": "integer",
            "description": "Number of recorded bind failures still within the window.",
            "example": 0
          },
          "graceLoginsUsed": {
            "type": "integer",
            "description": "Number of grace logins already consumed.",
            "example": 0
          },
          "policy": {
            "type": "string",
            "nullable": true,
            "description": "DN of the policy the status was computed with: the one the\naccount's `pwdPolicySubentry` names, else the default one.\n",
            "example": "cn=default,ou=policies,dc=example,dc=com"
          }
        }
      },
      "PasswordPolicyEntry": {
        "type": "object",
        "description": "A password policy entry. Only the attributes the entry holds are\nserved; durations are in **seconds**. `pwdAttribute` and\n`pwdCheckModule` are read-only.\n",
        "required": [
          "dn",
          "cn",
          "isDefault"
        ],
        "properties": {
          "dn": {
            "type": "string",
            "example": "cn=default,ou=policies,dc=example,dc=com"
          },
          "cn": {
            "type": "string",
            "example": "default"
          },
          "isDefault": {
            "type": "boolean",
            "description": "Whether this is the policy accounts without `pwdPolicySubentry` get."
          },
          "pwdAttribute": {
            "type": "string",
            "example": "userPassword"
          },
          "pwdMinAge": {
            "type": "integer"
          },
          "pwdMaxAge": {
            "type": "integer",
            "example": 7776000
          },
          "pwdInHistory": {
            "type": "integer",
            "example": 5
          },
          "pwdCheckQuality": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2
          },
          "pwdMinLength": {
            "type": "integer",
            "example": 12
          },
          "pwdMaxLength": {
            "type": "integer"
          },
          "pwdExpireWarning": {
            "type": "integer"
          },
          "pwdGraceAuthNLimit": {
            "type": "integer"
          },
          "pwdGraceExpiry": {
            "type": "integer"
          },
          "pwdLockout": {
            "type": "boolean"
          },
          "pwdLockoutDuration": {
            "type": "integer"
          },
          "pwdMaxFailure": {
            "type": "integer"
          },
          "pwdFailureCountInterval": {
            "type": "integer"
          },
          "pwdMustChange": {
            "type": "boolean"
          },
          "pwdAllowUserChange": {
            "type": "boolean"
          },
          "pwdSafeModify": {
            "type": "boolean"
          },
          "pwdMinDelay": {
            "type": "integer"
          },
          "pwdMaxDelay": {
            "type": "integer"
          },
          "pwdMaxIdle": {
            "type": "integer"
          },
          "pwdMaxRecordedFailure": {
            "type": "integer"
          },
          "pwdCheckModule": {
            "type": "string"
          },
          "pwdCheckModuleArg": {
            "type": "string",
            "description": "Configuration of the quality-check module, usually several lines."
          },
          "pwdUseCheckModule": {
            "type": "boolean"
          }
        }
      },
      "PasswordPolicyChanges": {
        "type": "object",
        "description": "Attributes to set, by name as in `PasswordPolicyEntry`. `null` removes\nthe attribute. On `PATCH`, `dn`, `cn`, `isDefault` and the read-only\nattributes are accepted only with the value the entry already has.\n",
        "additionalProperties": true,
        "example": {
          "pwdMaxAge": 31536000,
          "pwdLockout": true,
          "pwdExpireWarning": null
        }
      },
      "ExpiringAccount": {
        "type": "object",
        "description": "User whose password is expiring within the warning window.",
        "required": [
          "dn",
          "expiresAt",
          "daysUntilExpiration"
        ],
        "properties": {
          "dn": {
            "type": "string",
            "example": "uid=bob",
            "ou=users": null,
            "dc=example": null,
            "dc=com": null
          },
          "uid": {
            "type": "string",
            "example": "bob"
          },
          "displayName": {
            "type": "string",
            "example": "Bob Builder"
          },
          "mail": {
            "type": "string",
            "example": "bob@example.com"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "example": "2026-05-01T08:00:00.000Z"
          },
          "daysUntilExpiration": {
            "type": "integer",
            "example": 6
          }
        }
      },
      "LockedAccount": {
        "type": "object",
        "description": "User account that is currently locked.",
        "required": [
          "dn",
          "failureCount"
        ],
        "properties": {
          "dn": {
            "type": "string",
            "example": "uid=charlie",
            "ou=users": null,
            "dc=example": null,
            "dc=com": null
          },
          "uid": {
            "type": "string",
            "example": "charlie"
          },
          "displayName": {
            "type": "string",
            "example": "Charlie Chaplin"
          },
          "mail": {
            "type": "string",
            "example": "charlie@example.com"
          },
          "lockedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": "2026-04-24T14:30:00.000Z"
          },
          "failureCount": {
            "type": "integer",
            "example": 5
          }
        }
      },
      "PasswordValidationRequest": {
        "type": "object",
        "required": [
          "password"
        ],
        "properties": {
          "password": {
            "type": "string",
            "format": "password",
            "description": "The candidate password to validate.",
            "example": "S3cur3P@ss!"
          },
          "dn": {
            "type": "string",
            "description": "Optional user DN for context (not used in local validation).",
            "example": "uid=alice,ou=users,dc=example,dc=com"
          }
        }
      },
      "PasswordValidationResponse": {
        "type": "object",
        "required": [
          "valid",
          "errors"
        ],
        "properties": {
          "valid": {
            "type": "boolean",
            "description": "True when the password meets all complexity rules.",
            "example": true
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of failed rule descriptions (empty when valid).",
            "example": []
          }
        }
      },
      "RawAttribute": {
        "type": "object",
        "description": "Values of a single attribute. When `binary` is true the values are\nbase64-encoded octets rather than text.\n",
        "required": [
          "values",
          "binary"
        ],
        "properties": {
          "values": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "Alice Smith"
            ]
          },
          "binary": {
            "type": "boolean",
            "example": false
          }
        }
      },
      "RawEntry": {
        "type": "object",
        "description": "A directory entry with all its attributes, as stored.",
        "required": [
          "dn",
          "attributes"
        ],
        "properties": {
          "dn": {
            "type": "string",
            "example": "uid=alice,ou=users,dc=example,dc=com"
          },
          "attributes": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/RawAttribute"
            }
          }
        },
        "example": {
          "dn": "uid=alice,ou=users,dc=example,dc=com",
          "attributes": {
            "objectClass": {
              "values": [
                "top",
                "inetOrgPerson"
              ],
              "binary": false
            },
            "uid": {
              "values": [
                "alice"
              ],
              "binary": false
            },
            "cn": {
              "values": [
                "Alice Smith"
              ],
              "binary": false
            }
          }
        }
      },
      "RawChild": {
        "type": "object",
        "description": "Direct child of an entry, as needed to draw a tree node.",
        "required": [
          "dn",
          "rdn",
          "objectClass",
          "hasChildren"
        ],
        "properties": {
          "dn": {
            "type": "string",
            "example": "uid=alice",
            "ou=users": null,
            "dc=example": null,
            "dc=com": null
          },
          "rdn": {
            "type": "string",
            "example": "uid=alice"
          },
          "objectClass": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "top",
              "inetOrgPerson"
            ]
          },
          "hasChildren": {
            "type": "boolean",
            "description": "Whether the entry has at least one child. Only computed when the\n`children` query parameter is set, otherwise always false.\n",
            "example": false
          }
        }
      },
      "ScimUser": {
        "type": "object",
        "description": "SCIM 2.0 User resource (RFC 7643 §4.1).\n",
        "required": [
          "schemas",
          "userName"
        ],
        "properties": {
          "schemas": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "urn:ietf:params:scim:schemas:core:2.0:User"
            ]
          },
          "id": {
            "type": "string",
            "description": "Opaque server-assigned identifier.",
            "example": "2819c223-7f76-453a-919d-413861904646"
          },
          "externalId": {
            "type": "string",
            "description": "Provisioner-assigned identifier.",
            "example": 701984
          },
          "userName": {
            "type": "string",
            "description": "Unique user identifier (login name).",
            "example": "bjensen"
          },
          "name": {
            "type": "object",
            "properties": {
              "formatted": {
                "type": "string",
                "example": "Ms. Barbara J Jensen III"
              },
              "familyName": {
                "type": "string",
                "example": "Jensen"
              },
              "givenName": {
                "type": "string",
                "example": "Barbara"
              },
              "middleName": {
                "type": "string",
                "example": "Jane"
              },
              "honorificPrefix": {
                "type": "string",
                "example": "Ms."
              },
              "honorificSuffix": {
                "type": "string",
                "example": "III"
              }
            }
          },
          "displayName": {
            "type": "string",
            "example": "Babs Jensen"
          },
          "nickName": {
            "type": "string",
            "example": "Babs"
          },
          "profileUrl": {
            "type": "string",
            "example": "https://login.example.com/bjensen"
          },
          "title": {
            "type": "string",
            "example": "Tour Guide"
          },
          "userType": {
            "type": "string",
            "example": "Employee"
          },
          "preferredLanguage": {
            "type": "string",
            "example": "en-US"
          },
          "locale": {
            "type": "string",
            "example": "en-US"
          },
          "timezone": {
            "type": "string",
            "example": "America/Los_Angeles"
          },
          "active": {
            "type": "boolean",
            "example": true
          },
          "emails": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "string"
                },
                "display": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                },
                "primary": {
                  "type": "boolean"
                }
              }
            },
            "example": [
              {
                "value": "bjensen@example.com",
                "type": "work",
                "primary": true
              }
            ]
          },
          "phoneNumbers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                },
                "primary": {
                  "type": "boolean"
                }
              }
            }
          },
          "addresses": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "streetAddress": {
                  "type": "string"
                },
                "locality": {
                  "type": "string"
                },
                "region": {
                  "type": "string"
                },
                "postalCode": {
                  "type": "string"
                },
                "country": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                },
                "primary": {
                  "type": "boolean"
                }
              }
            }
          },
          "groups": {
            "type": "array",
            "description": "Groups the user belongs to (read-only).",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "string"
                },
                "$ref": {
                  "type": "string"
                },
                "display": {
                  "type": "string"
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "resourceType": {
                "type": "string",
                "example": "User"
              },
              "created": {
                "type": "string",
                "format": "date-time"
              },
              "lastModified": {
                "type": "string",
                "format": "date-time"
              },
              "location": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          }
        }
      },
      "ScimGroup": {
        "type": "object",
        "description": "SCIM 2.0 Group resource (RFC 7643 §4.2).\n",
        "required": [
          "schemas",
          "displayName"
        ],
        "properties": {
          "schemas": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "urn:ietf:params:scim:schemas:core:2.0:Group"
            ]
          },
          "id": {
            "type": "string",
            "description": "Opaque server-assigned identifier.",
            "example": "e9e30dba-f08f-4109-8486-d5c6a331660a"
          },
          "externalId": {
            "type": "string",
            "example": "00g1emaKYZTWRINFRGETl"
          },
          "displayName": {
            "type": "string",
            "example": "Tour Guides"
          },
          "members": {
            "type": "array",
            "description": "Members of the group.",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "string",
                  "description": "User or Group id"
                },
                "$ref": {
                  "type": "string"
                },
                "display": {
                  "type": "string"
                }
              }
            },
            "example": [
              {
                "value": "2819c223-7f76-453a-919d-413861904646",
                "display": "Babs Jensen"
              },
              {
                "value": "902c246b-6245-4190-8e05-00816be7344a",
                "display": "Mandy Pepperidge"
              }
            ]
          },
          "meta": {
            "type": "object",
            "properties": {
              "resourceType": {
                "type": "string",
                "example": "Group"
              },
              "created": {
                "type": "string",
                "format": "date-time"
              },
              "lastModified": {
                "type": "string",
                "format": "date-time"
              },
              "location": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          }
        }
      },
      "ScimListResponse": {
        "type": "object",
        "description": "SCIM 2.0 list query response (urn:ietf:params:scim:api:messages:2.0:ListResponse).\n",
        "required": [
          "schemas",
          "totalResults",
          "Resources"
        ],
        "properties": {
          "schemas": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "urn:ietf:params:scim:api:messages:2.0:ListResponse"
            ]
          },
          "totalResults": {
            "type": "integer",
            "description": "Total number of matching resources.",
            "example": 2
          },
          "startIndex": {
            "type": "integer",
            "description": "1-based index of the first result returned.",
            "example": 1
          },
          "itemsPerPage": {
            "type": "integer",
            "description": "Number of resources returned in this page.",
            "example": 2
          },
          "Resources": {
            "type": "array",
            "items": {},
            "description": "The matching resources."
          }
        }
      },
      "ScimError": {
        "type": "object",
        "description": "SCIM 2.0 error response (urn:ietf:params:scim:api:messages:2.0:Error).\n",
        "required": [
          "schemas",
          "status"
        ],
        "properties": {
          "schemas": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "urn:ietf:params:scim:api:messages:2.0:Error"
            ]
          },
          "status": {
            "type": "string",
            "description": "HTTP status code as a string.",
            "example": "404"
          },
          "scimType": {
            "type": "string",
            "description": "SCIM error type keyword (RFC 7644 §3.12). One of: invalidFilter,\ntooMany, uniqueness, mutability, invalidSyntax, invalidPath,\nnoTarget, invalidValue, invalidVers, sensitive.\n",
            "example": "mutability"
          },
          "detail": {
            "type": "string",
            "description": "Human-readable error description.",
            "example": "Resource 2819c223-7f76-453a-919d-413861904646 not found."
          }
        },
        "example": {
          "schemas": [
            "urn:ietf:params:scim:api:messages:2.0:Error"
          ],
          "status": "404",
          "detail": "Resource 2819c223-7f76-453a-919d-413861904646 not found."
        }
      },
      "ScimPatchOp": {
        "type": "object",
        "description": "SCIM 2.0 patch request body (urn:ietf:params:scim:api:messages:2.0:PatchOp,\nRFC 7644 §3.5.2).\n",
        "required": [
          "schemas",
          "Operations"
        ],
        "properties": {
          "schemas": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "urn:ietf:params:scim:api:messages:2.0:PatchOp"
            ]
          },
          "Operations": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "op"
              ],
              "properties": {
                "op": {
                  "type": "string",
                  "enum": [
                    "add",
                    "remove",
                    "replace",
                    "Add",
                    "Remove",
                    "Replace"
                  ]
                },
                "path": {
                  "type": "string",
                  "description": "SCIM attribute path (e.g. \"emails[type eq \\\"work\\\"].value\")."
                },
                "value": {
                  "description": "New value for the target attribute."
                }
              }
            }
          }
        },
        "example": {
          "schemas": [
            "urn:ietf:params:scim:api:messages:2.0:PatchOp"
          ],
          "Operations": [
            {
              "op": "replace",
              "path": "active",
              "value": false
            },
            {
              "op": "replace",
              "path": "emails[type eq \"work\"].value",
              "value": "bjensen@newdomain.example.com"
            }
          ]
        }
      },
      "ScimBulkRequest": {
        "type": "object",
        "description": "SCIM 2.0 Bulk request (urn:ietf:params:scim:api:messages:2.0:BulkRequest,\nRFC 7644 §3.7).\n",
        "required": [
          "schemas",
          "Operations"
        ],
        "properties": {
          "schemas": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "urn:ietf:params:scim:api:messages:2.0:BulkRequest"
            ]
          },
          "failOnErrors": {
            "type": "integer",
            "description": "Stop processing after this many errors. Omit or set to 0 to\ncontinue on errors and process all operations.\n",
            "example": 1
          },
          "Operations": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "method",
                "path"
              ],
              "properties": {
                "method": {
                  "type": "string",
                  "enum": [
                    "POST",
                    "PUT",
                    "PATCH",
                    "DELETE"
                  ]
                },
                "bulkId": {
                  "type": "string",
                  "description": "Temporary client-assigned identifier for this operation.\nSubsequent operations in the same request may reference the\ncreated resource as \"bulkId:<value>\".\n"
                },
                "path": {
                  "type": "string",
                  "description": "Resource path relative to the SCIM prefix (e.g. /Users or /Groups/123)."
                },
                "data": {
                  "description": "Request body for POST/PUT/PATCH operations."
                }
              }
            }
          }
        },
        "example": {
          "schemas": [
            "urn:ietf:params:scim:api:messages:2.0:BulkRequest"
          ],
          "failOnErrors": 1,
          "Operations": [
            {
              "method": "POST",
              "bulkId": "qwerty",
              "path": "/Users",
              "data": {
                "schemas": [
                  "urn:ietf:params:scim:schemas:core:2.0:User"
                ],
                "userName": "bjensen",
                "name": {
                  "familyName": "Jensen",
                  "givenName": "Barbara"
                }
              }
            },
            {
              "method": "DELETE",
              "path": "/Users/2819c223-7f76-453a-919d-413861904646"
            }
          ]
        }
      },
      "ScimBulkResponse": {
        "type": "object",
        "description": "SCIM 2.0 Bulk response (urn:ietf:params:scim:api:messages:2.0:BulkResponse).\n",
        "required": [
          "schemas",
          "Operations"
        ],
        "properties": {
          "schemas": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "urn:ietf:params:scim:api:messages:2.0:BulkResponse"
            ]
          },
          "Operations": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "method",
                "status"
              ],
              "properties": {
                "method": {
                  "type": "string",
                  "enum": [
                    "POST",
                    "PUT",
                    "PATCH",
                    "DELETE"
                  ]
                },
                "bulkId": {
                  "type": "string"
                },
                "location": {
                  "type": "string",
                  "description": "URL of the created/modified resource."
                },
                "version": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "description": "HTTP status code for this individual operation."
                },
                "response": {
                  "$ref": "#/components/schemas/ScimError",
                  "description": "Present only when the operation failed."
                }
              }
            }
          }
        },
        "example": {
          "schemas": [
            "urn:ietf:params:scim:api:messages:2.0:BulkResponse"
          ],
          "Operations": [
            {
              "method": "POST",
              "bulkId": "qwerty",
              "location": "https://example.com/scim/v2/Users/2819c223-7f76-453a-919d-413861904646",
              "status": "201"
            },
            {
              "method": "DELETE",
              "status": "204"
            }
          ]
        }
      },
      "ScimResourceType": {
        "type": "object",
        "description": "SCIM 2.0 ResourceType definition (RFC 7643 §6).",
        "required": [
          "schemas",
          "id",
          "name",
          "endpoint",
          "schema"
        ],
        "properties": {
          "schemas": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "urn:ietf:params:scim:schemas:core:2.0:ResourceType"
            ]
          },
          "id": {
            "type": "string",
            "example": "User"
          },
          "name": {
            "type": "string",
            "example": "User"
          },
          "endpoint": {
            "type": "string",
            "example": "/Users"
          },
          "description": {
            "type": "string",
            "example": "User Account"
          },
          "schema": {
            "type": "string",
            "example": "urn:ietf:params:scim:schemas:core:2.0:User"
          },
          "schemaExtensions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "schema": {
                  "type": "string"
                },
                "required": {
                  "type": "boolean"
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "resourceType": {
                "type": "string",
                "example": "ResourceType"
              },
              "location": {
                "type": "string"
              }
            }
          }
        }
      },
      "ScimSchema": {
        "type": "object",
        "description": "SCIM 2.0 Schema definition (RFC 7643 §7).",
        "required": [
          "schemas",
          "id",
          "name",
          "attributes"
        ],
        "properties": {
          "schemas": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "urn:ietf:params:scim:schemas:core:2.0:Schema"
            ]
          },
          "id": {
            "type": "string",
            "example": "urn:ietf:params:scim:schemas:core:2.0:User"
          },
          "name": {
            "type": "string",
            "example": "User"
          },
          "description": {
            "type": "string",
            "example": "User Account"
          },
          "attributes": {
            "type": "array",
            "items": {},
            "description": "Schema attribute definitions."
          },
          "meta": {
            "type": "object",
            "properties": {
              "resourceType": {
                "type": "string",
                "example": "Schema"
              },
              "location": {
                "type": "string"
              }
            }
          }
        }
      },
      "LdapSchema": {
        "type": "object",
        "description": "A JSON schema file served from the `schemas/` sub-directory of the\nstatic path.  These schemas describe LDAP entity types (users, groups,\norganizations, …) and are consumed by browser-embedded editors to\nrender attribute forms and validate input before submission.\n\nThe server replaces `__FOO_BAR__` placeholders on the fly with the\nvalue of the corresponding `--foo-bar` CLI option so that a single\nschema file can be shared between server-side validation and\nclient-side rendering. A schema that `extends` another is served\nmerged with it.\n",
        "additionalProperties": true,
        "example": {
          "entity": {
            "name": "standardUser",
            "mainAttribute": "uid",
            "objectClass": [
              "top",
              "inetOrgPerson"
            ],
            "singularName": "user",
            "pluralName": "users",
            "base": "ou=users,dc=example,dc=com"
          },
          "strict": true,
          "attributes": {
            "uid": {
              "type": "string",
              "required": true,
              "role": "identifier"
            },
            "cn": {
              "type": "string",
              "required": true
            }
          }
        }
      },
      "AppAccount": {
        "type": "object",
        "description": "An application-specific identity attached to a user.  App accounts\nallow a single principal user to authenticate as different named\n\"devices\" or \"applications\" each with its own credential, while\nsharing the same mail address.\n",
        "required": [
          "uid"
        ],
        "properties": {
          "uid": {
            "type": "string",
            "description": "Unique identifier of the app account.  Format:\n`<prefix>_c<8-digits>`, where `<prefix>` is the `:user` path param\n(the resolution value — the mail by default) sanitized into a\nuid-safe token (e.g. `alice@example.com` -> `alice_example_com`).\n",
            "example": "alice_example_com_c04729183"
          },
          "name": {
            "type": "string",
            "description": "Human-readable label (stored as LDAP `description`).",
            "example": "Work laptop"
          }
        },
        "example": {
          "uid": "alice_c04729183",
          "name": "Work laptop"
        }
      },
      "AppAccountCreated": {
        "type": "object",
        "description": "Response returned when a new app account is successfully created.\nThe one-time cleartext password is included here and never returned\nagain — callers must store it immediately.\n",
        "required": [
          "uid",
          "pwd",
          "mail"
        ],
        "properties": {
          "uid": {
            "type": "string",
            "description": "Identifier of the newly created app account.",
            "example": "alice_c04729183"
          },
          "pwd": {
            "type": "string",
            "description": "One-time cleartext password generated for this account.\nFormat: 6 blocks of 4 characters separated by `-`\n(e.g. `Ab3@-xYz!-9pQ#-Sv4$-mN8!-pQ5@`).\nOpenLDAP hashes it via ppolicy before storage.\n",
            "example": "Ab3@-xYz!-9pQ#-Sv4$-mN8!-pQ5@"
          },
          "mail": {
            "type": "string",
            "description": "Mail address of the principal account this app account belongs to.",
            "example": "alice@example.com"
          }
        },
        "example": {
          "uid": "alice_c04729183",
          "pwd": "Ab3@-xYz!-9pQ#-Sv4$-mN8!-pQ5@",
          "mail": "alice@example.com"
        }
      },
      "AppAccountCreate": {
        "type": "object",
        "description": "Request body for creating a new app account.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Optional human-readable label for the new account.",
            "example": "Work laptop"
          }
        },
        "example": {
          "name": "Work laptop"
        }
      },
      "JamesQuotaUsage": {
        "type": "object",
        "description": "Quota information for a mailbox user as returned by the James\nWebAdmin API (`GET /quota/users/{mail}`). Contains separate\ncounters for message count and storage size.\n",
        "properties": {
          "count": {
            "type": "object",
            "description": "Message-count quota.",
            "properties": {
              "used": {
                "type": "integer",
                "description": "Number of messages currently stored.",
                "example": 142
              },
              "max": {
                "type": "integer",
                "nullable": true,
                "description": "Maximum number of messages allowed. `null` means unlimited.\n",
                "example": 10000
              }
            }
          },
          "size": {
            "type": "object",
            "description": "Storage-size quota (bytes).",
            "properties": {
              "used": {
                "type": "integer",
                "description": "Bytes currently used.",
                "example": 524288000
              },
              "max": {
                "type": "integer",
                "nullable": true,
                "description": "Maximum bytes allowed. `null` means unlimited.\n",
                "example": 2147483648
              }
            }
          }
        },
        "example": {
          "count": {
            "used": 142,
            "max": 10000
          },
          "size": {
            "used": 524288000,
            "max": 2147483648
          }
        }
      },
      "FlatEntry": {
        "type": "object",
        "description": "A single LDAP entry from a flat branch. The `dn` field is always\npresent; all other fields depend on the concrete plugin's schema\n(e.g. `uid`, `cn`, `mail`, `sn` for users).\n",
        "required": [
          "dn"
        ],
        "properties": {
          "dn": {
            "type": "string",
            "description": "Fully-qualified distinguished name of the entry.",
            "example": "uid=alice,ou=users,dc=example,dc=com"
          }
        },
        "additionalProperties": {
          "oneOf": [
            {
              "type": "string"
            },
            {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          ]
        },
        "example": {
          "dn": "uid=alice,ou=users,dc=example,dc=com",
          "uid": "alice",
          "cn": "Alice Smith",
          "sn": "Smith",
          "mail": "alice@example.com"
        }
      },
      "FlatList": {
        "type": "object",
        "description": "A map of entries returned by the LIST endpoint, keyed by the\nentry's `mainAttribute` value (e.g. the `uid` for users). This\nis the `LdapList` shape (`Record<string, FlatEntry>`).\n",
        "additionalProperties": {
          "$ref": "#/components/schemas/FlatEntry"
        },
        "example": {
          "alice": {
            "dn": "uid=alice,ou=users,dc=example,dc=com",
            "uid": "alice",
            "cn": "Alice Smith",
            "mail": "alice@example.com"
          },
          "bob": {
            "dn": "uid=bob,ou=users,dc=example,dc=com",
            "uid": "bob",
            "cn": "Bob Jones",
            "mail": "bob@example.com"
          }
        }
      },
      "FlatCreate": {
        "type": "object",
        "description": "Body for creating a new entry. The plugin's `mainAttribute` field\n(e.g. `uid`) is required and used to build the DN. All other\nattributes are plugin-specific; unknown attributes are accepted\nunless the plugin's schema is in strict mode.\n",
        "additionalProperties": true,
        "example": {
          "uid": "carol",
          "cn": "Carol White",
          "sn": "White",
          "mail": "carol@example.com"
        }
      },
      "FlatModify": {
        "type": "object",
        "description": "Partial-update body. Supports `add`, `replace`, and `delete`\nmaps matching the LDAP modify operation. The `mainAttribute`\n(e.g. `uid`) cannot be changed through this endpoint — use the\nrename API instead.\n",
        "properties": {
          "add": {
            "type": "object",
            "description": "Attributes whose values are appended.",
            "additionalProperties": true
          },
          "replace": {
            "type": "object",
            "description": "Attributes whose values are replaced wholesale.",
            "additionalProperties": true
          },
          "delete": {
            "description": "Attributes (or specific values) to remove. Either an array\nof attribute names or a map of attribute → value(s).\n",
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "object",
                "additionalProperties": true
              }
            ]
          }
        },
        "example": {
          "replace": {
            "mail": "carol.white@example.com",
            "cn": "Carol White-Smith"
          }
        }
      },
      "FlatMoveRequest": {
        "type": "object",
        "description": "Body for moving an entry to a different organisational unit.\nRequires `ldap_organization_link_attribute` and\n`ldap_organization_path_attribute` to be defined in the plugin's\nschema.\n",
        "required": [
          "targetOrgDn"
        ],
        "properties": {
          "targetOrgDn": {
            "type": "string",
            "description": "DN of the destination organisational unit.",
            "example": "ou=engineering,ou=departments,dc=example,dc=com"
          }
        },
        "example": {
          "targetOrgDn": "ou=engineering,ou=departments,dc=example,dc=com"
        }
      },
      "FlatRenameRequest": {
        "type": "object",
        "description": "Body for changing the identifier of an entry. The field is named\n`newId` for every resource, whatever that resource calls its\n`mainAttribute`.\n",
        "required": [
          "newId"
        ],
        "properties": {
          "newId": {
            "type": "string",
            "description": "New value of the resource's `mainAttribute`. A value, not a DN.\n",
            "example": "bob"
          }
        },
        "example": {
          "newId": "bob"
        }
      },
      "FlatRenameResult": {
        "type": "object",
        "description": "Outcome of a rename: the entry's new DN, and how many references to\nthe old one were rewritten.\n",
        "properties": {
          "success": {
            "type": "boolean",
            "description": "False when a reference could not be rewritten."
          },
          "dn": {
            "type": "string",
            "description": "DN the entry now has."
          },
          "referencesUpdated": {
            "type": "integer",
            "description": "References rewritten to the new DN."
          },
          "referencesFailed": {
            "type": "array",
            "description": "References left naming the old DN, counted per attribute. The\nreferring entries are named in the server log, not here.\n",
            "items": {
              "type": "object",
              "properties": {
                "attribute": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "example": {
          "success": true,
          "dn": "uid=bob,ou=users,dc=example,dc=com",
          "referencesUpdated": 3
        }
      }
    }
  }
}
