nexu-io/open-design · error · Error

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

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.

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)

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.