{"record":{"id":"2f3bf5d10e19c53d","repo":"unslothai/unsloth","slug":"invalid-or-expired-api-key","errorCode":null,"errorMessage":"Invalid or expired API key","messagePattern":"Invalid or expired API key","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"studio/backend/auth/authentication.py","lineNumber":267,"sourceCode":"    return \"Invalid or expired API key\"\n\n\nasync def _get_current_credential(\n    credentials: HTTPAuthorizationCredentials, *, allow_password_change: bool\n) -> Tuple[str, Optional[str]]:\n    \"\"\"Validate the bearer and return ``(subject, credential generation)``.\n\n    The generation is the credential version this request actually authenticated\n    against. Routes that persist new credentials must bind their write to it, or\n    a reset landing mid-request would bless what it just revoked.\n    \"\"\"\n    token = credentials.credentials\n\n    # --- API key path (sk-unsloth-...) ---\n    if token.startswith(API_KEY_PREFIX):\n        verified = validate_api_key_with_credential(token)\n        if verified is None:\n            raise HTTPException(\n                status_code = status.HTTP_401_UNAUTHORIZED,\n                detail = _invalid_api_key_detail(token),\n            )\n        username, secret = verified\n        return username, credential_generation(secret)\n\n    # --- JWT path ---\n    subject = _decode_subject_without_verification(token)\n    if subject is None:\n        raise HTTPException(\n            status_code = status.HTTP_401_UNAUTHORIZED,\n            detail = \"Invalid token payload\",\n        )\n\n    record = get_user_and_secret(subject)\n    if record is None:\n        raise HTTPException(\n            status_code = status.HTTP_401_UNAUTHORIZED,","sourceCodeStart":249,"sourceCodeEnd":285,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/auth/authentication.py#L249-L285","documentation":"HTTP 401 raised on the API-key path when validate_api_key_with_credential(token) returns None, meaning the sk-unsloth-... bearer does not match any stored, current API key credential. The detail text comes from _invalid_api_key_detail(token), which typically distinguishes revoked vs unknown keys. This is the failure clients should treat as 're-create or fix the API key'.","triggerScenarios":"Sending Authorization: Bearer sk-unsloth-... where the key was revoked, deleted, regenerated, or typo'd; using a key issued by a different backend instance/database; whitespace or truncation corrupting the header value.","commonSituations":"Key rotated in the UI but the old key still baked into scripts/env files; copying keys between environments (dev key against prod); the key was reset via credential reset and older clients keep using it.","solutions":["Generate a fresh API key in studio settings and update the client's environment/config.","Confirm the key is sent verbatim: no leading/trailing whitespace, no shell expansion issues, full sk-unsloth-... string.","If the key was recently revoked or reset, re-issue it and update all consumers.","Verify the client points at the same backend instance that stored the key."],"exampleFix":"# before\nheaders = {\"Authorization\": \"Bearer sk-unsloth-OLD-REVOKED\"}\n\n# after\nheaders = {\"Authorization\": f\"Bearer {os.environ['UNSLOTH_API_KEY']}\"}  # freshly generated key","handlingStrategy":"validation","validationCode":"import re\nassert re.match(r'^sk-unsloth-[A-Za-z0-9_\\-]+$', key) and not key.isspace(), 'malformed API key'","typeGuard":"def is_wellformed_api_key(token: str) -> bool:\n    return bool(token) and token.startswith('sk-unsloth-') and len(token) > len('sk-unsloth-')","tryCatchPattern":"try:\n    call_api(headers=bearer(key))\nexcept HTTPStatusError as e:\n    if e.response.status_code == 401:\n        key = create_new_api_key_via_ui()  # or prompt the user","preventionTips":["Store API keys in env/secret managers; rotate by re-issuing and updating all consumers.","Never assume a previously working key is still valid after a reset or rotation event.","Send the key verbatim; strip whitespace when reading from config files."],"tags":["authentication","api-key","http-401","studio"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}