nexu-io/open-design · error · Error

no Leonardo.ai API key — configure it in Settings or set…

Error message

no Leonardo.ai API key — configure it in Settings or set LEONARDO_API_KEY

What it means

Thrown at the top of renderLeonardoImage when credentials.apiKey is falsy. The Leonardo.ai provider requires a bearer token; the daemon did not find one in the resolved ProviderConfig. The message lists all valid routes (Settings UI, LEONARDO_API_KEY env var) so the user knows the three ways to supply it.

Solutions

  1. Open Settings in the web UI, find the Leonardo.ai provider, paste the API key, and save.
  2. Or set LEONARDO_API_KEY in the daemon's environment and restart: export LEONARDO_API_KEY=<key> (then restart the daemon so it inherits the var).
  3. Verify the key is valid at cloud.leonardo.ai before configuring — an expired token will pass this guard but fail later at submit.
  4. Confirm the daemon process actually inherited the env var (ps e <pid> | grep LEONARDO or check tools-dev logs).

Example fix

// before — daemon started without the key
$ pnpm tools-dev
# → renderLeonardoImage throws [462]

// after
$ export LEONARDO_API_KEY=$(cat ~/.secrets/leonardo.key)
$ pnpm tools-dev
Defensive patterns

Strategy: validation

Validate before calling

// Validate credential presence before dispatching to the Leonardo renderer.
function assertLeonardoCreds(credentials: ProviderConfig): void {
  if (!credentials.apiKey || typeof credentials.apiKey !== 'string') {
    throw new Error(
      'no Leonardo.ai API key — configure it in Settings or set LEONARDO_API_KEY',
    );
  }
}

Type guard

function hasLeonardoCredential(c: ProviderConfig): c is ProviderConfig & { apiKey: string } {
  return typeof c?.apiKey === 'string' && c.apiKey.length > 0;
}

Prevention

When it happens

Trigger: User invokes image generation on a leonardo-* model while no Leonardo credential is configured in Settings and no LEONARDO_API_KEY environment variable is set on the daemon process.

Common situations: First-time use of a Leonardo model without setup; env var set on a different shell than the daemon was started from; key cleared/removed from Settings but model selection still defaults to Leonardo; deploy of a fresh daemon without migrating the credentials store.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/540fd28dba53658c. Report an issue: GitHub.

Appendix: source

Thrown at apps/daemon/src/media/index.ts:2175

function openRouterAspectFor(aspect?: string): string {
  // OpenRouter normalises aspect ratios across providers. Our
  // MEDIA_ASPECTS vocabulary is a strict subset — pass known values
  // through, default to 16:9 for video.
  if (
    aspect === '1:1'
    || aspect === '16:9'
    || aspect === '9:16'
    || aspect === '4:3'
    || aspect === '3:4'
  ) {
    return aspect;
  }
  return '16:9';
}

async function renderLeonardoImage(ctx: MediaContext, credentials: ProviderConfig): Promise<RenderResult> {
  if (!credentials.apiKey) {
    throw new Error(
      'no Leonardo.ai API key — configure it in Settings or set LEONARDO_API_KEY',
    );
  }
  const baseUrl = (credentials.baseUrl || 'https://cloud.leonardo.ai/api/rest/v1').replace(/\/$/, '');
  
  // Map model IDs to Leonardo.ai platform model IDs
  const modelMap: Record<string, string> = {
    'leonardo-phoenix': '6b645e3a-d64f-4341-a6d8-7a3690fbf042',  // Phoenix
    'leonardo-kino-xl': 'aa77f04e-3eec-4034-9c07-d0f619684628',  // Kino XL
    'leonardo-flux-dev': 'b2614463-296c-462a-9586-aafdb8f00e36', // FLUX.1 [dev]
    'leonardo-flux-schnell': '1dd50843-d653-4516-a8e3-f0238ee453ff', // FLUX.1 [schnell]
    'leonardo-anime-pastel': '1e60896f-3c26-4296-8ecc-53e2afecc132', // Anime Pastel Dream
  };
  
  const platformModelId = modelMap[ctx.model];
  if (!platformModelId) {
    throw new Error(`unsupported leonardo.ai model: ${ctx.model}`);
  }

View on GitHub (pinned to 5be4028344)