{"record":{"id":"5f3113a5d45f8a15","repo":"langgenius/dify","slug":"the-annotation-quota-has-reached-the-limit-of-your","errorCode":null,"errorMessage":"The annotation quota has reached the limit of your subscription.","messagePattern":"The annotation quota has reached the limit of your subscription\\.","errorType":"http","errorClass":null,"httpStatus":403,"severity":"warning","filePath":"api/controllers/console/wraps.py","lineNumber":216,"sourceCode":"                apps = features.apps\n                documents_upload_quota = features.documents_upload_quota\n                annotation_quota_limit = features.annotation_quota_limit\n                if resource == \"members\" and 0 < members.limit <= members.size:\n                    abort(403, \"The number of members has reached the limit of your subscription.\")\n                elif resource == \"apps\" and 0 < apps.limit <= apps.size:\n                    abort(403, \"The number of apps has reached the limit of your subscription.\")\n                elif resource == \"documents\" and 0 < documents_upload_quota.limit <= documents_upload_quota.size:\n                    # The api of file upload is used in the multiple places,\n                    # so we need to check the source of the request from datasets\n                    source = request.args.get(\"source\") or request.form.get(\"source\")\n                    if source == \"datasets\":\n                        abort(403, \"The number of documents has reached the limit of your subscription.\")\n                    else:\n                        return view(*args, **kwargs)\n                elif resource == \"workspace_custom\" and not features.can_replace_logo:\n                    abort(403, \"The workspace custom feature has reached the limit of your subscription.\")\n                elif resource == \"annotation\" and 0 < annotation_quota_limit.limit < annotation_quota_limit.size:\n                    abort(403, \"The annotation quota has reached the limit of your subscription.\")\n                else:\n                    return view(*args, **kwargs)\n\n            return view(*args, **kwargs)\n\n        return decorated\n\n    return interceptor\n\n\ndef cloud_edition_billing_knowledge_limit_check[**P, R](\n    resource: str,\n) -> Callable[[Callable[P, R]], Callable[P, R]]:\n    def interceptor(view: Callable[P, R]):\n        @wraps(view)\n        def decorated(*args: P.args, **kwargs: P.kwargs):\n            _, current_tenant_id = current_account_with_tenant()\n            features = FeatureService.get_features(current_tenant_id, exclude_vector_space=True)","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/wraps.py#L198-L234","documentation":"HTTP 403 from `cloud_edition_billing_resource_check(\"annotation\")`. When `resource == \"annotation\"` and `0 < annotation_quota_limit.limit < annotation_quota_limit.size` (strictly less than — note the asymmetry vs other quotas), the decorator aborts with `The annotation quota has reached the limit of your subscription.` Blocks new annotation uploads once usage exceeds the limit.","triggerScenarios":"Adding an annotation on a tenant whose annotation count already exceeds the plan's annotation quota (`size > limit`, with `limit > 0`).","commonSituations":"Free-plan tenant with a small annotation quota after heavy annotate use; mismatch between UI counter and server count after concurrent annotation adds; note the strict `<` means the cap trips one annotation later than the other resources' `<=`.","solutions":["Upgrade the workspace plan to raise the annotation quota.","Delete old/unused annotations to bring `size` back under `limit`.","Surface `annotation_quota_limit` (`limit`/`size`) in the annotation UI and disable add at the ceiling.","Be aware the check is `limit < size` (strict), so plan for off-by-one vs the members/apps/documents checks."],"exampleFix":"// before\nawait addAnnotation(payload)  // 403 once size > limit\n// after\nconst { annotation_quota_limit: q } = await getFeatures()\nif (q.limit > 0 && q.size >= q.limit) {\n  alert('Annotation quota reached — upgrade or remove annotations.')\n} else {\n  await addAnnotation(payload)\n}","handlingStrategy":"validation","validationCode":"from services.feature_service import FeatureService\n\n_, tenant_id = current_account_with_tenant()\nfeatures = FeatureService.get_features(tenant_id, exclude_vector_space=True)\nq = features.annotation_quota_limit\n\n# server uses strict limit < size, so block once size >= limit\ndef can_add_annotation() -> bool:\n    return not (q.limit > 0 and q.size >= q.limit)\n\nif not can_add_annotation():\n    # surface upgrade prompt instead of adding annotation\n    ...","typeGuard":"def under_annotation_quota(limit: int, size: int) -> bool:\n    # note: server check is limit < size (strict); client guard at size >= limit is safe\n    return not (limit > 0 and size >= limit)","tryCatchPattern":"try:\n    resp = client.post(\"/apps/<app>/annotation-reply\", ...)\nexcept HTTPError as err:\n    if err.response.status_code == 403 and \"annotation\" in err.response.text:\n        # annotation quota reached — upgrade or remove annotations\n        ...\n    raise","preventionTips":["Surface annotation_quota_limit (limit/size) in the annotation UI; block add at the ceiling.","Be aware the server uses strict limit < size — guard at size >= limit client-side.","Delete unused annotations to free quota.","Upgrade the plan to raise the annotation quota."],"tags":["billing","quota","annotation","http-403"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}