{"record":{"id":"a76b5222ac1b7ef9","repo":"calcom/cal.diy","slug":"missing-giphy-api-key","errorCode":null,"errorMessage":"Missing Giphy api_key","messagePattern":"Missing Giphy api_key","errorType":"http","errorClass":"HttpError","httpStatus":400,"severity":"error","filePath":"packages/app-store/giphy/lib/giphyManager.ts","lineNumber":8,"sourceCode":"import { HttpError } from \"@calcom/lib/http-error\";\n\nimport getAppKeysFromSlug from \"../../_utils/getAppKeysFromSlug\";\n\nconst checkGiphyApiKey = async () => {\n  const appKeys = await getAppKeysFromSlug(\"giphy\");\n  if (typeof appKeys.api_key === \"string\") return appKeys.api_key;\n  throw new HttpError({ statusCode: 400, message: \"Missing Giphy api_key\" });\n};\n\nexport const searchGiphy = async (locale: string, keyword: string, offset = 0) => {\n  const apiKey = await checkGiphyApiKey();\n  const queryParams = new URLSearchParams({\n    api_key: apiKey,\n    q: keyword,\n    limit: \"1\",\n    offset: String(offset),\n    // Contains images that are broadly accepted as appropriate and commonly witnessed by people in a public environment.\n    rating: \"g\",\n    lang: locale,\n  });\n  const response = await fetch(`https://api.giphy.com/v1/gifs/search?${queryParams.toString()}`, {\n    method: \"GET\",\n    headers: {\n      Accept: \"application/json\",\n    },","sourceCodeStart":1,"sourceCodeEnd":26,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/app-store/giphy/lib/giphyManager.ts#L1-L26","documentation":"Thrown by `checkGiphyApiKey` when the `api_key` field on the Giphy app's stored keys is not a string. Keys come from `getAppKeysFromSlug(\"giphy\")`, which reads `app.keys` off the `App` row (returning `{}` when absent). If admins never configured a key, or stored it as a non-string JSON value, this fires as `HttpError` **400**. It guards every Giphy API call (`searchGiphy`, `getGiphyById`).","triggerScenarios":"Calling `searchGiphy(locale, keyword, offset)` or `getGiphyById(giphyId)` — i.e. the GIF picker in the event/booking UI — when the Giphy app's `api_key` is unset, empty (but non-string-typed), or stored as an object/number in the app-store admin settings (`App.keys` JSON).","commonSituations":"Fresh deployment where the admin never entered a Giphy key; key was put in an env var instead of the app-store settings UI; `App` row seeded without keys; key saved as a JSON object `{ api_key: { ... } }` by mistake.","solutions":["In the admin app-store settings for Giphy, set `api_key` to a valid Giphy API key string and save, then verify the `App` row's `keys` JSON contains `\"api_key\": \"<string>\"`.","Add a UI preflight: fetch the Giphy app keys and disable/hide the GIF picker when `api_key` is missing so users never reach the 400.","Tighten the guard to also reject blank strings (`!key.trim()`) so a whitespace-only value is treated as missing."],"exampleFix":"// before\nconst checkGiphyApiKey = async () => {\n  const appKeys = await getAppKeysFromSlug(\"giphy\");\n  if (typeof appKeys.api_key === \"string\") return appKeys.api_key;\n  throw new HttpError({ statusCode: 400, message: \"Missing Giphy api_key\" });\n};\n// after\nconst checkGiphyApiKey = async () => {\n  const appKeys = await getAppKeysFromSlug(\"giphy\");\n  const key = typeof appKeys.api_key === \"string\" ? appKeys.api_key.trim() : \"\";\n  if (key) return appKeys.api_key as string;\n  throw new HttpError({ statusCode: 400, message: \"Missing Giphy api_key\" });\n};","handlingStrategy":"validation","validationCode":"// Check the configured key before invoking any Giphy manager function\nimport getAppKeysFromSlug from \"@calcom/app-store/_utils/getAppKeysFromSlug\";\n\nasync function getGiphyApiKeyOrNull() {\n  const keys = await getAppKeysFromSlug(\"giphy\");\n  return typeof keys.api_key === \"string\" && keys.api_key.trim() ? (keys.api_key as string) : null;\n}\n\n// usage: skip the GIF picker when null\nconst apiKey = await getGiphyApiKeyOrNull();\nif (!apiKey) { /* hide/disable GIF picker, do not call searchGiphy */ }","typeGuard":"function hasGiphyApiKey(keys: Record<string, unknown>): keys is { api_key: string } {\n  return typeof keys.api_key === \"string\" && (keys.api_key as string).trim().length > 0;\n}","tryCatchPattern":null,"preventionTips":["Configure the Giphy api_key in the app-store admin settings before enabling the GIF picker UI.","Gate the feature behind a preflight key check so users never hit the 400.","Store the key as a flat string in App.keys, never as a nested object or number."],"tags":["app-store","giphy","config","api-key","http-400"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}