lfnovo/open-notebook · error · InvalidInputError

Speaker profile '{value}' not found

Error message

Speaker profile '{value}' not found

What it means

InvalidInputError (mapped to HTTP 400) raised when creating/updating an episode profile and the provided speaker_config cannot be resolved to a SpeakerProfile record — it is neither a valid record ID nor a known profile name.

Source

Thrown at api/routers/episode_profiles.py:71

        name=profile.name,
        description=profile.description or "",
        speaker_config=profile.speaker_config,
        speaker_config_name=speaker_name,
        outline_llm=profile.outline_llm,
        transcript_llm=profile.transcript_llm,
        language=profile.language,
        default_briefing=profile.default_briefing,
        num_segments=profile.num_segments,
        max_tokens=profile.max_tokens,
    )


async def _resolve_speaker_config(value: str) -> SpeakerProfile:
    """Resolve an incoming speaker_config (record ID, or name for backward
    compatibility) to the referenced SpeakerProfile."""
    speaker = await SpeakerProfile.resolve(value)
    if not speaker:
        raise InvalidInputError(f"Speaker profile '{value}' not found")
    return speaker


@router.get("/episode-profiles", response_model=List[EpisodeProfileResponse])
async def list_episode_profiles():
    """List all available episode profiles"""
    try:
        profiles = await EpisodeProfile.get_all(order_by="name asc")
        speaker_names = await _speaker_names_by_id()
        return [
            _profile_to_response(
                p, speaker_names.get(p.speaker_config) if p.speaker_config else None
            )
            for p in profiles
        ]
    except HTTPException:
        raise
    except OpenNotebookError:

View on GitHub (pinned to a7de90d38a)

Solutions

  1. GET /api/speaker-profiles to list valid ids/names and use one of them
  2. If resolving by name, ensure the name matches exactly (case/whitespace)
  3. If the speaker profile was deleted, recreate it or clear speaker_config
  4. Prefer the record ID over the name for stability

Example fix

// before
{"name": "podcast", "speaker_config": "Default Speaker"}
// after
{"name": "podcast", "speaker_config": "speaker_profile:abc123"}
Defensive patterns

Strategy: validation

Validate before calling

profiles = (await client.get("/api/speaker-profiles")).json()
valid = {p["id"] for p in profiles} | {p["name"] for p in profiles}
if payload.get("speaker_config") not in valid:
    payload["speaker_config"] = profiles[0]["id"]  # or prompt the user

Type guard

def is_valid_speaker_ref(value: str, profiles: list[dict]) -> bool:
    return value in {p["id"] for p in profiles} | {p["name"] for p in profiles}

Try / catch

try:
    await create_episode_profile(payload)
except HTTPError as e:
    if e.response.status_code == 400 and "not found" in e.response.text:
        # refresh speaker list and re-submit with a valid id
        ...

Prevention

When it happens

Trigger: POST or PUT /api/episode-profiles with speaker_config set to a deleted SpeakerProfile id, a misspelled name, or an id from a different environment/database.

Common situations: Speaker profile deleted after the frontend cached its name; copying request bodies between environments; using a display name when the record was stored under a different name after SpeakerProfile.resolve semantics changed.

Related errors


AI-assisted analysis of lfnovo/open-notebook@a7de90d38a (2026-08-27). Data as JSON: /api/errors/f82d08e9ca862de0. Report an issue: GitHub.