{"record":{"id":"9262910955a359e2","repo":"makeplane/plane","slug":"given-api-token-is-not-valid","errorCode":null,"errorMessage":"Given API token is not valid","messagePattern":"Given API token is not valid","errorType":"exception","errorClass":"AuthenticationFailed","httpStatus":401,"severity":"error","filePath":"apps/api/plane/api/middleware/api_authentication.py","lineNumber":38,"sourceCode":"    \"\"\"\n\n    www_authenticate_realm = \"api\"\n    media_type = \"application/json\"\n    auth_header_name = \"X-Api-Key\"\n\n    def get_api_token(self, request):\n        return request.headers.get(self.auth_header_name)\n\n    def validate_api_token(self, token):\n        try:\n            api_token = APIToken.objects.get(\n                Q(Q(expired_at__gt=timezone.now()) | Q(expired_at__isnull=True)),\n                token=token,\n                is_active=True,\n                user__is_active=True,\n            )\n        except APIToken.DoesNotExist:\n            raise AuthenticationFailed(\"Given API token is not valid\")\n\n        # save api token last used\n        api_token.last_used = timezone.now()\n        api_token.save(update_fields=[\"last_used\"])\n        return (api_token.user, api_token.token)\n\n    def authenticate(self, request):\n        token = self.get_api_token(request=request)\n        if not token:\n            return None\n\n        # Validate the API token\n        user, token = self.validate_api_token(token)\n        return user, token\n","sourceCodeStart":20,"sourceCodeEnd":53,"githubUrl":"https://github.com/makeplane/plane/blob/1c8a60f858d8472aa56e29994ec1c7926da2c6ce/apps/api/plane/api/middleware/api_authentication.py#L20-L53","documentation":"validate_api_token queries APIToken with conditions: token matches, is_active=True, user__is_active=True, and (expired_at > now OR expired_at is null). If APIToken.DoesNotExist is raised, the DRF AuthenticationFailed is thrown with this message, producing an HTTP 401. This is the standard API-token auth path in apps/api/plane/api/middleware.","triggerScenarios":"Request with X-API-Key header set to a token that is wrong, revoked (is_active=False), owned by a deactivated user, or past expired_at with no null expiry.","commonSituations":"Rotated/revoked token still used by an integration; clock skew between client and server expiring the token early; user deactivated but external script still runs; typo in the header value.","solutions":["Regenerate the API token in the workspace settings and update the client.","Confirm the token's user account is active and not suspended.","Check the token's expired_at; if set and passed, issue a new token.","Verify the header name matches auth_header_name (default X-API-Key) and the value has no leading/trailing whitespace."],"exampleFix":"# before\ncurl -H 'X-API-Key: stale-or-revoked' https://api/v1/...\n\n# after\ncurl -H 'X-API-Key: <newly generated token>' https://api/v1/...","handlingStrategy":"try-catch","validationCode":"# before each call, ensure token is non-empty and recently issued\nimport os\nassert os.environ['PLANE_API_KEY'].strip(), 'missing API token'","typeGuard":null,"tryCatchPattern":"try:\n    client.get('/work-items/')\nexcept AuthenticationFailed as e:\n    if str(e) == 'Given API token is not valid':\n        rotate_token(); retry()","preventionTips":["Rotate revoked tokens promptly","Store tokens in env/secret manager, not code","Strip whitespace from header values","Confirm the owning user is active"],"tags":["auth","api-token","django-rest","authentication"],"backgroundTag":null,"analyzedSha":"1c8a60f858d8472aa56e29994ec1c7926da2c6ce","analyzedAt":"2026-08-12T14:44:31.636Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}