jamiepine/voicebox · error · HTTPException

Profile not found

Error message

Profile not found

What it means

GET /profiles/{profile_id} calls profiles.get_profile; if it returns a falsy value (None) the route raises HTTP 404 'Profile not found'. This is the standard missing-resource guard: the id did not match any row in the profiles table. The lookup is by the string profile_id column, so a typo, a deleted row, or an id from another environment all collapse into this one response.

Source

Thrown at backend/routes/profiles.py:117

                    "voice_id": speaker_id,
                    "name": display_name,
                    "gender": gender,
                    "language": lang,
                }
                for speaker_id, display_name, gender, lang, _desc in QWEN_CUSTOM_VOICES
            ],
        }
    return {"engine": engine, "voices": []}

@router.get("/profiles/{profile_id}", response_model=models.VoiceProfileResponse)
async def get_profile(
    profile_id: str,
    db: Session = Depends(get_db),
):
    """Get a voice profile by ID."""
    profile = await profiles.get_profile(profile_id, db)
    if not profile:
        raise HTTPException(status_code=404, detail="Profile not found")
    return profile


@router.put("/profiles/{profile_id}", response_model=models.VoiceProfileResponse)
async def update_profile(
    profile_id: str,
    data: models.VoiceProfileCreate,
    db: Session = Depends(get_db),
):
    """Update a voice profile."""
    try:
        profile = await profiles.update_profile(profile_id, data, db)
        if not profile:
            raise HTTPException(status_code=404, detail="Profile not found")
        return profile
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Verify the id against the current list returned by GET /profiles.
  2. On the client, treat a 404 here as 'evict this id from local cache and fall back to the list view'.
  3. Confirm you are sending the string profile_id returned at create time, not an internal numeric primary key.
  4. Trim whitespace from the path segment before sending.
Defensive patterns

Strategy: validation

Validate before calling

# Verify existence before the targeted GET
async def get_profile_or_evict(client, profile_id):
    listing = (await client.get("/profiles")).json()
    ids = {p["id"] for p in listing}
    if profile_id not in ids:
        return None            # caller should evict the stale id
    return await client.get(f"/profiles/{profile_id}")

Try / catch

resp = await client.get(f"/profiles/{profile_id}")
if resp.status_code == 404:
    evict_from_cache(profile_id)
    show_default_view()
else:
    resp.raise_for_status()

Prevention

When it happens

Trigger: GET /profiles/{profile_id} with an id that was never created; an id of a profile deleted via DELETE /profiles/{id}; an id copy-pasted from a different deployment/database; a UUID with a stray leading/trailing space.

Common situations: Frontend cached a profile list, user deleted the profile in another tab, then the stale UI issues a GET; a bookmarked URL pointing at an old profile; passing the DB numeric PK instead of the string profile_id.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/7d1d2942a33ab596. Report an issue: GitHub.