{"record":{"id":"f7959fc7ff215830","repo":"calcom/cal.diy","slug":"apikeysservice-provided-api-key-is-not-valid","errorCode":null,"errorMessage":"ApiKeysService - provided api key is not valid.","messagePattern":"ApiKeysService - provided api key is not valid\\.","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"apps/api/v2/src/modules/api-keys/services/api-keys.service.ts","lineNumber":67,"sourceCode":"        },\n      },\n      input: {\n        note: createApiKeyInput.note,\n        neverExpires: !!createApiKeyInput.apiKeyNeverExpires,\n        expiresAt: apiKeyExpiresAt,\n        teamId: createApiKeyInput.teamId,\n      },\n    });\n\n    return apiKey;\n  }\n\n  async refreshApiKey(authUserId: number, apiKey: string, refreshApiKeyInput: RefreshApiKeyInput) {\n    const strippedApiKey = stripApiKey(apiKey, this.config.get<string>(\"api.keyPrefix\"));\n    const apiKeyHash = sha256Hash(strippedApiKey);\n    const apiKeyInDb = await this.apiKeysRepository.getApiKeyFromHash(apiKeyHash);\n    if (!apiKeyInDb) {\n      throw new UnauthorizedException(\"ApiKeysService - provided api key is not valid.\");\n    }\n\n    const newApiKey = await this.createApiKey(authUserId, {\n      ...refreshApiKeyInput,\n      note: apiKeyInDb.note || undefined,\n      teamId: apiKeyInDb.teamId || undefined,\n    });\n\n    await this.apiKeysRepository.deleteById(apiKeyInDb.id);\n\n    return newApiKey;\n  }\n}\n","sourceCodeStart":49,"sourceCodeEnd":81,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/api-keys/services/api-keys.service.ts#L49-L81","documentation":"Thrown by ApiKeysService.refreshApiKey when the SHA-256 hash of the stripped API key does not match any record in the apiKey table via apiKeysRepository.getApiKeyFromHash. The refresh endpoint requires the caller to provide their existing valid API key, and if that key cannot be found (deleted, expired, wrong prefix, or incorrect), the refresh is rejected with UnauthorizedException (HTTP 401).","triggerScenarios":"Calling POST /v2/api-keys/refresh with a key that was already refreshed (the old key is deleted at line 76 during refresh). Using a key from a different environment (dev key against prod API). The API_KEY_PREFIX mismatch causing stripApiKey to remove the wrong prefix, producing a different hash than what's stored.","commonSituations":"Attempting to refresh an already-refreshed key (the old key is immediately deleted). Copying a key with truncation or extra characters. Environment mismatch: key created in staging but used against production. The key expired and was cleaned up by a background job.","solutions":["Verify the API key is the current active key (not one that was already refreshed or deleted).","Confirm the api.keyPrefix config value matches between the client and server so stripApiKey removes the correct prefix.","If the key is lost or deleted, create a new one via POST /v2/api-keys instead of attempting to refresh.","Check for trailing whitespace or newlines in the key value, which would produce a different SHA-256 hash."],"exampleFix":"// before: attempting to refresh an already-refreshed key\nawait client.post('/v2/api-keys/refresh', {\n  body: { apiKey: oldKeyAlreadyDeleted }\n});\n\n// after: use the current key, or create a new one if lost\ntry {\n  await client.post('/v2/api-keys/refresh', {\n    body: { apiKey: currentActiveKey }\n  });\n} catch (e) {\n  if (e.statusCode === 401) {\n    // key is invalid; create a fresh one\n    const { apiKey } = await client.post('/v2/api-keys', { body: { note: 'replacement' } });\n    return apiKey;\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Before refreshing, verify the key format matches the expected prefix\nconst canRefreshKey = (key: string, prefix: string): boolean => {\n  return key.startsWith(prefix) && key.length > prefix.length + 5;\n};\nif (!canRefreshKey(currentKey, 'cal_')) {\n  throw new Error('Current API key is malformed; create a new key instead of refreshing.');\n}","typeGuard":null,"tryCatchPattern":"// Handle refresh failure by falling back to key creation\nconst refreshOrCreate = async (client: ApiClient, currentKey: string): Promise<string> => {\n  try {\n    const { apiKey } = await client.post('/v2/api-keys/refresh', {\n      body: { apiKey: currentKey }\n    });\n    return apiKey;\n  } catch (err: any) {\n    if (err?.response?.status === 401) {\n      // Key is invalid or already refreshed; create a new one\n      const { apiKey } = await client.post('/v2/api-keys', {\n        body: { note: 'Replacement key' }\n      });\n      return apiKey;\n    }\n    throw err;\n  }\n};","preventionTips":["Store the most recent API key from the refresh response immediately and update all clients atomically.","Never attempt to refresh a key that was already refreshed — the old key is deleted at refresh time.","Keep an audit log of key refreshes to track which key is current.","If the key is lost, use the create endpoint rather than retrying refresh."],"tags":["authentication","api-key","nestjs","api-v2","unauthorized"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}