{"record":{"id":"d654bf235215d748","repo":"nexu-io/open-design","slug":"no-nano-banana-api-key-configure-it-in-settings","errorCode":null,"errorMessage":"no Nano Banana API key — configure it in Settings or set OD_NANOBANANA_API_KEY","messagePattern":"no Nano Banana API key — configure it in Settings or set OD_NANOBANANA_API_KEY","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/media/index.ts","lineNumber":1673,"sourceCode":"  } else {\n    throw new Error('grok image response missing b64_json/url');\n  }\n  // xAI's Imagine returns JPEG by default (no format option in the API\n  // surface), but PNG/WebP are technically possible. Sniff the magic\n  // bytes so the on-disk extension matches reality — saving JPEG bytes\n  // as `.png` confuses Finder previews and any downstream consumer that\n  // trusts the extension.\n  return {\n    bytes,\n    providerNote: `grok/${ctx.wireModel} · ${aspectRatio} · ${bytes.length} bytes`,\n    suggestedExt: sniffImageExt(bytes),\n  };\n}\n\nasync function renderNanoBananaImage(ctx: MediaContext, credentials: ProviderConfig): Promise<RenderResult> {\n  const apiKey = credentials.apiKey;\n  if (!apiKey) {\n    throw new Error(\n      'no Nano Banana API key — configure it in Settings or set OD_NANOBANANA_API_KEY',\n    );\n  }\n\n  const baseUrl = (credentials.baseUrl || NANOBANANA_DEFAULT_BASE_URL).replace(/\\/$/, '');\n  const wireModel = (credentials.model || ctx.wireModel || NANOBANANA_DEFAULT_MODEL).trim();\n  const body = {\n    contents: [{\n      parts: [{\n        text: ctx.prompt || 'A high-quality reference image.',\n      }],\n    }],\n    generationConfig: {\n      responseModalities: ['IMAGE'],\n      imageConfig: {\n        aspectRatio: nanoBananaAspectFor(ctx.aspect),\n        imageSize: NANOBANANA_DEFAULT_IMAGE_SIZE,\n      },","sourceCodeStart":1655,"sourceCodeEnd":1691,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/media/index.ts#L1655-L1691","documentation":"Thrown by renderNanoBananaImage (Google Gemini 'Nano Banana' / gemini-2.5-flash-image provider) before any network call when credentials.apiKey is empty. It is the contract guard ensuring a key is present so the request can be authorized against the v1beta generateContent endpoint.","triggerScenarios":"User selects the Nano Banana provider in Settings without entering an API key, OD_NANOBANANA_API_KEY env var is unset, or the credentials store failed to persist the key. The error message names both remediation paths explicitly.","commonSituations":"First-time provider setup where the user picked the model but skipped the key field; deploying the daemon in a fresh environment without exporting OD_NANOBANANA_API_KEY; key cleared by a settings reset.","solutions":["Open Settings in the web UI and paste the Nano Banana / Google AI Studio API key into the provider config.","Or export OD_NANOBANANA_API_KEY in the daemon process environment and restart.","If the key was set but the error persists, verify credentials.apiKey is actually loaded by the provider credentials loader (check the media provider registry)."],"exampleFix":"// before\nconst apiKey = credentials.apiKey;\nif (!apiKey) {\n  throw new Error('no Nano Banana API key — configure it in Settings or set OD_NANOBANANA_API_KEY');\n}\n\n// after — fall back to env at the call site so headless setups work without UI\nconst apiKey = credentials.apiKey || process.env.OD_NANOBANANA_API_KEY;\nif (!apiKey) {\n  throw new Error('no Nano Banana API key — configure it in Settings or set OD_NANOBANANA_API_KEY');\n}","handlingStrategy":"validation","validationCode":"// Pre-flight check before invoking renderNanoBananaImage\nfunction hasNanoBananaCredentials(creds: ProviderConfig): boolean {\n  return Boolean(creds.apiKey && creds.apiKey.trim());\n}\n\n// at the call site, before dispatching:\nif (ctx.providerId === 'nanobanana' && !hasNanoBananaCredentials(credentials)) {\n  return { kind: 'config-error', message: 'Add your Nano Banana API key in Settings or set OD_NANOBANANA_API_KEY.' };\n}","typeGuard":"function nanoBananaConfigured(creds: ProviderConfig, env = process.env): boolean {\n  return Boolean((creds.apiKey && creds.apiKey.trim()) || env.OD_NANOBANANA_API_KEY);\n}","tryCatchPattern":"// caller wraps the dispatch\ntry {\n  await renderNanoBananaImage(ctx, credentials);\n} catch (e) {\n  if (/no Nano Banana API key/.test(String(e))) {\n    return presentConfigPrompt('nanobanana');\n  }\n  throw e;\n}","preventionTips":["Run a credential pre-flight in the provider registry before dispatching to renderNanoBananaImage.","Allow OD_NANOBANANA_API_KEY as an env fallback at the call site so headless setups work.","Surface a Settings deep-link in the UI when the key is missing rather than a raw stack error.","Re-validate credentials after any config import/migration."],"tags":["nano-banana","gemini","image-generation","credentials","config","provider-api"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}