Skip to content

[Feature]: Standardize Flask ML API errors behind a machine-readable error envelope (backward compatible) #986

Description

@pavsoss

[Feature]: Standardize Flask ML API errors behind a machine-readable error envelope (backward compatible)

Summary

Error responses from the Flask ML API are inconsistent in shape, which makes reliable client-side error handling impossible. Depending on which handler you hit you get:

  • {"error": "No text provided"} — /predict, /spam-insights, /importance
  • {"error": str(e)} — several except blocks (also leaks raw exception strings)
  • {"success": false, "error": "Forbidden: ..."} — zero-trust 403 / /api/wordcloud
  • {"error": "Internal server error", "request_id": ...} — the global 500 handler
  • {"error": "rate_limit_exceeded", "message": ..., "retry_after": 60} — the 429 handler

There is no error code vocabulary and no consistent place for request_id, so a consumer can't branch on error type or correlate a failure with server logs (even though g.request_id already exists).

This proposes a single, typed error envelope, added additively so existing clients that read error as a string keep working.

Proposed envelope

{
  "error": "Human readable message",          // kept for backward compatibility
  "error_detail": {
    "code": "NO_TEXT_PROVIDED",               // stable ErrorCode enum
    "message": "Human readable message",
    "request_id": "…"                          // always present, from g.request_id
  }
}

Legacy top-level error string stays, so nothing breaks (the README explicitly promises /predict backward compatibility). New/modern clients read error_detail.

Proposed solution & PR split

PR 1/2 — Error module + handlers + core endpoints (Hard)

File Change
backend/errors.py (new) ErrorCode(StrEnum), ApiError(Exception), error_response(code, message, status, request_id) builder
backend/api.py register errorhandler(ApiError) + 400/403/404/429/500 → envelope (reusing existing 429 payload fields); migrate /predict input-validation returns, /feedback, /feedback/stats, /spam-insights, /importance, /api/wordcloud to the builder
backend/tests/test_error_envelope.py (new) every migrated 4xx/5xx has error (str) and error_detail.{code,message,request_id}; codes are stable; legacy field preserved

Standalone: the envelope is live and tested for the core endpoints.

PR 2/2 — Email / OAuth / IMAP endpoints + contract test (Hard)

File Change
backend/api.py migrate /analyze-email-header, /gmail/*, /outlook/*, /scan-emails, /imap/connect handlers to raise ApiError with codes (stop returning raw str(e))
backend/errors.py add provider/auth/upstream error codes
backend/tests/test_error_contract.py (new) drive each error path and assert the envelope + that no handler leaks a bare str(e) without a code
README.md "Error format" reference section

Standalone: completes coverage + documents the contract; safe to merge after PR-1.

Difficulty: Hard (each sub-PR)

Touching every error path in a large module, introducing an enum-backed exception type + handlers, preserving backward compatibility, and adding contract tests that assert the schema is depth work

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions