A full-stack tool that classifies API error messages using keyword matching, generates an explanation with the OpenAI API, and stores each analysis in PostgreSQL.
Problem: When an API call fails, developers have to manually read the error message, guess the likely cause, and search for a fix.
Solution: This project accepts an API name and an error message, matches the message against a small set of predefined error patterns (rate limiting, authentication, server errors, timeouts), returns a likely cause and suggested next steps, and adds a plain-language explanation generated by OpenAI. Every analysis is saved to a PostgreSQL table.
Intended use case: A portfolio/learning project demonstrating a layered FastAPI backend, a React frontend, SQLAlchemy persistence, and a third-party AI API integration — not a production debugging platform.
- Keyword-based classification of API error messages into one of four categories, or
Unclassifiedif nothing matches - A confidence percentage based on how many of a category's keywords were matched (see How Confidence Is Calculated — this is not a machine learning or statistical model)
- AI-generated explanation of the error via the OpenAI API (
gpt-3.5-turbo) - Persistent storage of every analysis (API name, original error message, category, confidence, AI explanation) in PostgreSQL via SQLAlchemy
- Single-page React UI to submit an error and view the result
flowchart TD
A[React Frontend] -->|POST /analyze| B[FastAPI Route]
B --> C[Pydantic Validation - ErrorRequest]
C --> D[analyze_error_logic]
D --> E[Keyword Matching against ERROR_PATTERNS]
E --> F{Match found?}
F -->|Yes| G[OpenAI API call]
F -->|No| H[Static Unclassified fallback]
G --> I[SQLAlchemy - insert DebugSession row]
H --> I
I --> J[PostgreSQL]
I --> K[JSON Response]
K --> A
Request flow, in words:
- The React form sends
api_nameanderror_messagetoPOST /analyze. - FastAPI validates the payload against the
ErrorRequestPydantic model (both fields are required strings; a missing/invalid field returns HTTP 422 automatically). analyze_error_logic()lowercases the message into a separate variable (the original casing is preserved for storage) and scores it against each entry inERROR_PATTERNSby counting matched keywords.- If a category matched at least one keyword, the OpenAI API is called once to generate a short explanation. If nothing matched, a static
"Unclassified"result is used and OpenAI is not called — this keeps behavior predictable and avoids spending an API call on a message the app can't categorize. - The result (including the AI explanation) is written to the
debug_sessionstable. The database session is wrapped intry/except/finally, so a failed write rolls back and the connection still closes. - The full result — category, cause, solutions, confidence score, and AI explanation — is returned as JSON and rendered by the frontend.
Frontend: React 19, Vite, Tailwind CSS, Axios
Backend: FastAPI, Pydantic
Database: PostgreSQL, SQLAlchemy (no migrations — tables are created via Base.metadata.create_all() on startup)
AI: OpenAI API (gpt-3.5-turbo)
Deployment (as configured, unverified beyond code): The frontend's default backend URL points to a Render deployment, and CORS is scoped to a Vercel origin. There is no Dockerfile, CI configuration, or infrastructure-as-code in this repository.
The core logic lives in backend/services/analyze_service.py:
ERROR_PATTERNS(inbackend/utils/error_patterns.py) is a hardcoded Python list of four dictionaries, each with acategory, a list ofkeywords, acause, and a list ofsolutions.- For each category, the code counts how many of its keywords appear as substrings in the lowercased error message.
- The category with the highest keyword-match count is selected as
best_match. Ties go to whichever category was checked first. - If no category matched any keyword, the result is
Unclassifiedwith a static cause/solutions and no OpenAI call.
confidence = (matched_keywords_in_best_category / total_keywords_in_that_category) * 100
This is a simple ratio, not a probability, not a machine learning prediction, and not a statistical confidence interval. It only reflects how many of one category's predefined keywords happened to appear in the message. A message that happens to contain 2 of a 4-keyword category's terms shows "50%" — that is the entire calculation.
| Method | Endpoint | Purpose |
|---|---|---|
| GET | / |
Health check — returns {"message": "Backend is running successfully"} |
| POST | /analyze |
Accepts {"api_name": string, "error_message": string}, returns category, cause, solutions, confidence score, and AI explanation |
Example request:
{
"api_name": "Stripe",
"error_message": "429 Too Many Requests"
}Example response:
{
"category": "Rate Limiting",
"cause": "Too many requests in short time",
"solutions": [
"Use retry logic",
"Reduce request frequency",
"Add exponential backoff"
],
"confidence_score": "50.0%",
"ai_explanation": "..."
}If no keyword matches any category, category is "Unclassified", confidence_score is "0%", and ai_explanation is a static message — OpenAI is not called in this case.
ai-api-debugger/
├── backend/
│ ├── main.py # FastAPI app, CORS, router registration
│ ├── database.py # SQLAlchemy engine/session setup
│ ├── models/
│ │ ├── request_models.py # Pydantic request schema
│ │ └── db_models.py # SQLAlchemy DebugSession table
│ ├── routes/
│ │ └── analyze.py # POST /analyze route
│ ├── services/
│ │ ├── analyze_service.py # Core matching, scoring, persistence logic
│ │ └── ai_service.py # OpenAI API call
│ ├── utils/
│ │ └── error_patterns.py # Hardcoded category/keyword definitions
│ ├── requirements.txt
│ └── .env.example
├── frontend/
│ ├── src/
│ │ ├── App.jsx # Single-page UI: form + result display
│ │ └── main.jsx
│ ├── package.json
│ └── .env.example
└── requirements.txt
cd backend python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate pip install -r requirements.txt cp .env.example .env # then fill in real values uvicorn main:app --reload
cd frontend npm install cp .env.example .env # optional — see below npm run dev
backend/.env
DATABASE_URL=your_postgresql_connection_string_here
OPENAI_API_KEY=your_openai_api_key_here
frontend/.env
VITE_API_URL=http://localhost:8000
If VITE_API_URL is not set, the frontend falls back to the deployed production backend URL. Set it to http://localhost:8000 (or wherever the backend is running) for local development.
The app does not include migrations. On startup, Base.metadata.create_all(bind=engine) in main.py creates the debug_sessions table if it doesn't already exist, provided DATABASE_URL points to a reachable PostgreSQL instance.
- Service layer separated from the route.
routes/analyze.pyonly handles HTTP concerns (path, request/response); all matching, scoring, AI-calling, and persistence logic lives inservices/analyze_service.py. This keeps the route trivial to read and the business logic testable independently of FastAPI. - AI calls are conditional, not unconditional. OpenAI is only called when a keyword match is found. This was a deliberate choice to keep behavior predictable and avoid calling a paid, network-dependent API for input the app can't meaningfully classify.
- Matching uses a separate normalized variable. The original
error_messagestring is preserved unmodified for storage; a separatenormalized_message(lowercased) is used only for keyword comparison, so stored data reflects what the user actually submitted. - Database writes are wrapped in try/except/finally. A failed insert rolls back the transaction and the session is always closed, whether the write succeeds or fails — this is a deliberately minimal error-handling pattern, not a connection pool or retry system.
- Classification is a hardcoded keyword list covering four categories — it is not machine learning, not NLP, and will misclassify or fail to classify any error message that doesn't share vocabulary with
ERROR_PATTERNS. - The "confidence score" is a simple keyword-match ratio, not a statistically meaningful confidence measure.
- No authentication — anyone with the API URL can call
/analyzeand write to the database. - No automated tests.
- No database migrations (schema changes require manual intervention).
- No rate limiting on the OpenAI call — repeated requests will incur API cost per matched request.
- No pagination, retrieval, or listing endpoint for past analyses — data is stored but never read back through the API.
- Single-file React component — fine at this size, but would need to be split up if the UI grew.
- Add a
GET /sessionsendpoint to list past analyses from the database - Add automated tests for the keyword-matching logic and the API endpoint
- Add Alembic migrations instead of
create_all() - Expand
ERROR_PATTERNSto cover more error categories - Add basic input length limits and rate limiting on
/analyze
Not currently included. To add them, capture:
- The main form (empty state)
- A submitted analysis showing category, confidence score, cause, AI explanation, and solutions
- The
/docsSwagger UI page for the/analyzeendpoint
and reference them here as assets/dashboard.png, assets/result.png, assets/swagger.png.
Anuman Shailesh Modi
GitHub: https://github.com/anumanmodi