{"record":{"id":"c1613405db9407c8","repo":"nextcloud/server","slug":"too-many-addressbook-or-calendar-share-requests","errorCode":null,"errorMessage":"Too many addressbook or calendar share requests","messagePattern":"Too many addressbook or calendar share requests","errorType":"exception","errorClass":"OCA\\DAV\\Connector\\Sabre\\Exception\\TooManyRequests","httpStatus":429,"severity":"warning","filePath":"apps/dav/lib/DAV/Security/RateLimiting.php","lineNumber":43,"sourceCode":"\t}\n\n\t/**\n\t * @throws TooManyRequests\n\t */\n\tpublic function check(): void {\n\t\t$user = $this->userSession->getUser();\n\t\tif ($user === null) {\n\t\t\treturn;\n\t\t}\n\n\t\t$identifier = 'share-addressbook-or-calendar';\n\t\t$userLimit = $this->config->getValueInt('dav', 'rateLimitShareAddressbookOrCalendar', 100);\n\t\t$userPeriod = $this->config->getValueInt('dav', 'rateLimitPeriodShareAddressbookOrCalendar', 3600);\n\n\t\ttry {\n\t\t\t$this->limiter->registerUserRequest($identifier, $userLimit, $userPeriod, $user);\n\t\t} catch (IRateLimitExceededException $e) {\n\t\t\tthrow new TooManyRequests('Too many addressbook or calendar share requests', 0, $e);\n\t\t}\n\t}\n}\n","sourceCodeStart":25,"sourceCodeEnd":47,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/dav/lib/DAV/Security/RateLimiting.php#L25-L47","documentation":"HTTP 429 thrown by the DAV rate-limiting helper (OCA\\DAV\\Connector\\Sabre\\Exception\\TooManyRequests) whenever a logged-in user exceeds the share-operation quota for calendars and address books. The counter is tracked per user under the identifier 'share-addressbook-or-calendar' with defaults of 100 operations per 3600 seconds, read from app config dav/rateLimitShareAddressbookOrCalendar and dav/rateLimitPeriodShareAddressbookOrCalendar. It is enforced in the DAV sharing plugin before every POST whose XML root is an {http://owncloud.org/ns}share document, covering both set (share) and remove (unshare) operations.","triggerScenarios":"More than 100 POST requests with Content-Type application/xml and an <oc:share> body against /remote.php/dav/calendars/<user>/<calendar>/ or /remote.php/dav/addressbooks/<user>/<book>/ within one hour by the same user; e.g. a provisioning or sync script that issues one share POST per sharee in a loop.","commonSituations":"User-management or migration scripts iterating over many users and sharing a calendar/address book with each; CalDAV clients (Thunderbird etc.) re-issuing invites during account setup; CI suites hammering share endpoints; deployments where the 100/hour default was lowered.","solutions":["Wait for the rate-limit window (default 3600 s) to elapse before issuing more share requests","Batch share changes so one POST carries up to 10 set/remove elements instead of one POST per change","Raise the quota for a known automation account: occ config:app:set dav rateLimitShareAddressbookOrCalendar --value 1000 (and rateLimitPeriodShareAddressbookOrCalendar for the window)","Run bulk provisioning via occ commands (e.g. dav:create-calendar, dav:create-address-book) or the OCS Share API instead of CalDAV share POSTs"],"exampleFix":"// before: one POST per sharee -> 150 requests/hour -> HTTP 429\nfor (const uid of sharees) {\n  await davPost(`/remote.php/dav/calendars/${owner}/cal1/`, shareXml([uid]));\n}\n// after: batch <=10 sharees per request, and raise the quota for migrations\n// shell: occ config:app:set dav rateLimitShareAddressbookOrCalendar --value 1000\nfor (const batch of chunk(sharees, 10)) {\n  await davPost(`/remote.php/dav/calendars/${owner}/cal1/`, shareXml(batch));\n}","handlingStrategy":"retry","validationCode":"// client-side token bucket mirroring the server defaults (100 per 3600 s)\nconst bucket = { tokens: 100, cap: 100, last: Date.now(), periodMs: 3600_000 };\nfunction canIssueShareRequest() {\n  const now = Date.now();\n  bucket.tokens = Math.min(bucket.cap, bucket.tokens + ((now - bucket.last) / bucket.periodMs) * bucket.cap);\n  bucket.last = now;\n  if (bucket.tokens < 1) return false;\n  bucket.tokens -= 1;\n  return true;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await davPost(calendarUrl, shareXml(batch));\n} catch (e) {\n  if (e.status === 429) {\n    const waitSec = Number(e.headers['retry-after'] ?? 3600);\n    await sleep(waitSec * 1000); // one scheduled retry, honoring the window\n    return davPost(calendarUrl, shareXml(batch));\n  }\n  throw e;\n}","preventionTips":["Batch up to 10 set/remove elements per POST","Provision masses via occ commands or the OCS Share API, which bypass the per-user DAV limit","Raise dav/rateLimitShareAddressbookOrCalendar for dedicated automation accounts","Track remaining quota client-side instead of retrying blindly"],"tags":["dav","caldav","carddav","rate-limit","sharing","http-429"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}