jamiepine/voicebox · error · HTTPException

{str(e)}

Error message

{str(e)}

What it means

Returned by POST /stories when stories.create_story raises any Exception (HTTP 400). The handler catches a broad Exception and forwards str(e) as detail, so the message is whatever the service threw. Because the catch is non-specific, the detail could range from validation to DB constraint errors.

Source

Thrown at backend/routes/stories.py:32

router = APIRouter()


@router.get("/stories", response_model=list[models.StoryResponse])
async def list_stories(db: Session = Depends(get_db)):
    """List all stories."""
    return await stories.list_stories(db)


@router.post("/stories", response_model=models.StoryResponse)
async def create_story(
    data: models.StoryCreate,
    db: Session = Depends(get_db),
):
    """Create a new story."""
    try:
        return await stories.create_story(data, db)
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))


@router.get("/stories/{story_id}", response_model=models.StoryDetailResponse)
async def get_story(
    story_id: str,
    db: Session = Depends(get_db),
):
    """Get a story with all its items."""
    story = await stories.get_story(story_id, db)
    if not story:
        raise HTTPException(status_code=404, detail="Story not found")
    return story


@router.put("/stories/{story_id}", response_model=models.StoryResponse)
async def update_story(
    story_id: str,
    data: models.StoryCreate,

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Read detail — it is str(e) from the service and usually names the failing constraint.
  2. Validate the StoryCreate payload (title, required fields, metadata serializability) before posting.
  3. If detail looks like an internal traceback rather than a user error, suspect a server bug and check backend logs.
  4. Avoid collisions with existing story slugs/titles if uniqueness is enforced.

Example fix

// before
await api.post('/stories', story);

// after
ensureStoryCreate(story); // checks title non-empty, metadata JSON-serializable
await api.post('/stories', story);
Defensive patterns

Strategy: validation

Validate before calling

function validateStoryCreate(s) {
  if (!s || typeof s.title !== 'string' || !s.title.trim()) throw new Error('title required');
  if (s.metadata != null) JSON.stringify(s.metadata);
}

Type guard

function isStoryCreate(v) { return !!v && typeof v.title === 'string' && v.title.trim().length > 0; }

Try / catch

try { await api.post('/stories', story); }
catch (e) { if (e.response?.status === 400) showUser(e.response.data.detail); throw e; }

Prevention

When it happens

Trigger: Submitting a StoryCreate body whose fields violate a constraint inside create_story (e.g., invalid title length, duplicate slug, malformed metadata). Also any unexpected service-layer exception surfaces here as 400.

Common situations: Missing required StoryCreate fields that pass Pydantic but fail DB constraints. Duplicate unique key (slug/title). Unserializable metadata payload. The broad catch can mask server-side bugs as 400.

Related errors


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