{"record":{"id":"57f8a81e0baa1319","repo":"nextcloud/server","slug":"addressbook-limit-reached","errorCode":null,"errorMessage":"AddressBook limit reached","messagePattern":"AddressBook limit reached","errorType":"http","errorClass":"Sabre\\DAV\\Exception\\Forbidden","httpStatus":403,"severity":"error","filePath":"apps/dav/lib/CardDAV/Security/CardDavRateLimitingPlugin.php","lineNumber":82,"sourceCode":"\t\t\t\t\t$this->config->getValueInt('dav', 'rateLimitPeriodAddressBookCreation', 3600),\n\t\t\t\t\t$user\n\t\t\t\t);\n\t\t\t} catch (RateLimitExceededException $e) {\n\t\t\t\tthrow new TooManyRequests('Too many addressbooks created', 0, $e);\n\t\t\t}\n\n\t\t\t$addressBookLimit = $this->config->getValueInt('dav', 'maximumAdressbooks', 10);\n\t\t\tif ($addressBookLimit === -1) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t$numAddressbooks = $this->cardDavBackend->getAddressBooksForUserCount('principals/users/' . $user->getUID());\n\n\t\t\tif ($numAddressbooks >= $addressBookLimit) {\n\t\t\t\t$this->logger->warning('Maximum number of address books reached', [\n\t\t\t\t\t'addressbooks' => $numAddressbooks,\n\t\t\t\t\t'addressBookLimit' => $addressBookLimit,\n\t\t\t\t]);\n\t\t\t\tthrow new Forbidden('AddressBook limit reached', 0);\n\t\t\t}\n\t\t}\n\t}\n\n}\n","sourceCodeStart":64,"sourceCodeEnd":88,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/dav/lib/CardDAV/Security/CardDavRateLimitingPlugin.php#L64-L88","documentation":"After the rate check, CardDavRateLimitingPlugin enforces a hard per-user cap: it counts existing books via getAddressBooksForUserCount('principals/users/<uid>') and compares against dav maximumAdressbooks (default 10; -1 disables the check). Meeting or exceeding the cap throws Forbidden('AddressBook limit reached') (HTTP 403). Note the config key's historical typo: it is maximumAdressbooks, not maximumAddressbooks.","triggerScenarios":"MKCOL of a new addressbook when the user already owns at least maximumAdressbooks books (default 10); scripted provisioning running past the cap.","commonSituations":"Power users or CRM integrations needing more than 10 books; admins setting 'maximumAddressbooks' (correctly spelled) and wondering why nothing changes; imports from other groupware.","solutions":["Delete or merge unused addressbooks to get below the cap","Raise the cap with the exact key: occ config:app:set dav maximumAdressbooks --value='50' ('-1' disables it)","Verify the effective value: occ config:app:get dav maximumAdressbooks"],"exampleFix":"# before\nMKCOL /addressbooks/users/alice/book-11/   (alice already has 10)\n-> 403 AddressBook limit reached\n\n# after\nocc config:app:set dav maximumAdressbooks --value='50'\nMKCOL /addressbooks/users/alice/book-11/   -> 201 Created","handlingStrategy":"validation","validationCode":"$books = $client->propFind('/remote.php/dav/addressbooks/users/' . $user . '/', [], 1);\n$count = max(0, count($books) - 1); // minus the collection itself\nif ($count >= 10 /* dav maximumAdressbooks default */) {\n    throw new AddressBookQuotaError('delete a book or ask the admin to raise dav maximumAdressbooks');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $client->request('MKCOL', $uri);\n} catch (\\Sabre\\HTTP\\ClientHttpException $e) {\n    if ($e->getResponse()->getStatus() === 403 && str_contains($e->getMessage(), 'limit reached')) {\n        // prompt the user to delete an addressbook or raise the cap\n    }\n}","preventionTips":["Track the per-user cap when provisioning (default 10; config key is maximumAdressbooks, typo included)","Offer addressbook cleanup once the user approaches the cap","'-1' disables the limit - set it deliberately, not by accident"],"tags":["carddav","dav","quota","http-403","mkcol","config-typo"],"backgroundTag":"resource-quota-exceeded","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}