Owner: Engineering Team | Last Updated: 2026-01-30 | Status: Current
This page documents all error responses returned by the WWAI backend API. Because the backend does not use a single consistent error format, frontend consumers must check multiple fields when extracting error messages. Each error is documented with its exact field name, value, triggering condition, and recommended resolution.
The WWAI backend returns errors using different field names depending on the endpoint and the error type. There is no single unified error schema. The frontend must check multiple fields in priority order.
| Field | Used By | Example |
|---|---|---|
detail |
Most endpoints (Django REST Framework default) | {"detail": "Authentication credentials were not provided."} |
error |
Some custom endpoints | {"error": "Processing failed"} |
message |
Some custom endpoints | {"message": "Invalid input"} |
email[0] |
Registration email validation | {"email": ["A user with that email already exists"]} |
Used in components and API wrappers that rely on Axios:
const message = error.response?.data?.message
|| error.response?.data?.error
|| error.response?.data?.detail
|| t('fallbackMessage')
Used in server actions and fetch-based API calls:
const errorData = await response.json().catch(() => ({}))
const errorMessage = errorData.detail
|| errorData.message
|| errorData.error
|| t('errorOccurred')
Important: Always attempt to parse the response body as JSON before accessing error fields. Some 500 errors return non-JSON bodies (plain text or HTML), so the
.catch(() => ({}))fallback is required.
Returned when the request body fails validation or contains invalid data.
| Endpoint | Error Field & Value | Trigger | Resolution |
|---|---|---|---|
POST /api/user/register/ |
email[0]: "A user with that email already exists" |
Attempting to register with an email already in the system | Prompt the user to log in instead, or use a different email |
POST /api/user/register/ |
email[0]: "Enter a valid email address." |
Malformed email format | Validate email format client-side before submission |
POST /api/humanizer/ |
Varies | Missing required fields in the humanizer request body (e.g., missing text field) |
Ensure all required fields are present: text, readability, purpose |
POST /api/detector/ |
Varies | Missing required fields in the detector request body | Ensure the text field is provided and non-empty |
| File upload endpoints | Varies | File exceeds 15 MB size limit or is an unsupported format | Validate file size (<= 15 MB) and format client-side before upload |
POST /api/user/password-reset/ |
email[0]: "No account found with this email" |
Email not registered | Show user-friendly message; do not reveal whether the email exists (security consideration) |
// Registration error handling
if (error.response?.status === 400) {
const data = error.response.data
if (data.email && Array.isArray(data.email)) {
// Display the first email validation error
showError(data.email[0])
} else {
showError(data.detail || data.message || t('validationError'))
}
}
Returned when authentication is missing or invalid. This is the most common error during normal usage due to JWT token expiration.
| Error Field & Value | Trigger | Resolution |
|---|---|---|
detail: "Authentication credentials were not provided." |
Request sent without an Authorization header, or the header is empty |
Add Authorization: Bearer <access_token> header to the request |
detail: "Given token not valid for any token type" |
The JWT access token has expired or is malformed | Refresh the token via POST /api/user/refresh/ using the refresh token |
code: "token_not_valid" |
Accompanies the above; the code field provides a machine-readable identifier |
Same as above |
NextAuth: RefreshAccessTokenError |
The refresh token itself has expired or been invalidated server-side | Full re-login is required. Redirect the user to the sign-in page |
1. API call returns 401
2. Frontend calls POST /api/user/refresh/ with { refresh: "<refresh_token>" }
a. Success (200) -> Extract new access token, retry original request
b. Failure (401) -> Refresh token expired, redirect to login
// app/api/actions.ts - Centralized 401 handling
const apiCall = async (method: string, url: string, data: any, token: string) => {
try {
const response = await axios({
method,
url,
data,
headers: { Authorization: `Bearer ${token}` }
})
return response.data
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 401) {
return { error: { status: 401, message: 'Your session expired!' } }
}
throw error
}
}
Returned when the user is authenticated but lacks permission to perform the requested action. In WWAI, most 403 errors are subscription and credit related, particularly on the humanizer endpoint.
These errors are returned by the humanizer endpoint and require specific UI handling.
detail Value |
Meaning | Frontend Handling |
|---|---|---|
"Your subscription is paused" |
The user's subscription is in a paused state | Show the pause/resume modal. The user must resume their subscription before humanizing text |
"word_limit" |
The submitted text exceeds the plan's per-request word limit | Show a word limit warning with the user's current limit. Suggest splitting text or upgrading |
"Your content exceeds the limit of your plan" |
Per-request word cap exceeded (alternative phrasing) | Show upgrade prompt with plan comparison |
"You have not enough credits" |
The user's monthly word credit balance is depleted | Show credit purchase option or plan upgrade prompt |
| Scenario | Typical detail Value |
Resolution |
|---|---|---|
| Free user accessing paid feature | Varies by endpoint | Show upgrade prompt directing to pricing page |
| Premium readability option selected on free plan | Varies | Indicate that the selected readability level requires a paid plan |
| Premium purpose option selected on free plan | Varies | Indicate that the selected purpose requires a paid plan |
if (error.response?.status === 403) {
const detail = error.response.data?.detail
if (detail === 'Your subscription is paused') {
showPauseResumeModal()
} else if (detail === 'word_limit' || detail === 'Your content exceeds the limit of your plan') {
showWordLimitWarning(userPlan.perRequestLimit)
} else if (detail === 'You have not enough credits') {
showCreditsDepletedModal()
} else {
showUpgradePrompt()
}
}
Returned when the requested resource does not exist or the URL is malformed.
| Common Cause | Detail | Fix |
|---|---|---|
| Missing trailing slash in URL | Django returns 404 for paths without trailing slashes | Always include trailing slashes: /api/user/login/ not /api/user/login |
| Deleted document or version | The document or document version has been removed | Handle gracefully in the UI; redirect to the documents list |
| Wrong API path | Endpoint does not exist | Consult the API Reference Overview for correct endpoint paths |
| Invalid document ID | The UUID in the URL does not match any existing resource | Verify the document ID; it may have been deleted by the user |
Django Trailing Slash Requirement: This is the most common cause of unexpected 404 errors during development. Django's
APPEND_SLASHsetting may redirect, but API calls typically do not follow redirects automatically. Always use trailing slashes in API URLs.
Returned when the user has exceeded their plan's usage limits. WWAI enforces limits at two levels: monthly word credits and per-request word limits.
| Plan | Monthly Word Limit |
|---|---|
| Free | 300 words/month |
| Starter | 10,000 words/month |
| Pro | 55,000 words/month |
| Unlimited | Unlimited |
| Plan | Max Words Per Request |
|---|---|
| Free | 300 words |
| Starter | 500 words |
| Pro | 1,200 words |
| Unlimited | 1,700 words |
When a 429 is received:
Note: The 403 errors
"word_limit"and"You have not enough credits"serve a similar purpose to 429 and may be returned instead depending on the endpoint. Frontend code should handle both status codes for credit/limit scenarios.
Returned when an unhandled exception occurs on the backend. These errors are not user-actionable and should be logged for engineering investigation.
| Common Cause | Debug Approach |
|---|---|
| Unhandled backend exception | Check Sentry for the stack trace and error context |
| AI model failure or timeout | Check the model health dashboard; the AI provider may be experiencing an outage |
| Database connection timeout | Check backend application logs and database connection pool metrics |
| External detector service timeout | Some detector scores may return as null or false; handle missing scores gracefully in the UI |
| Memory or resource exhaustion | Check server resource monitoring (CPU, memory, disk) |
if (error.response?.status === 500) {
// Show generic error message to user
showError(t('somethingWentWrong'))
// Log to Sentry for engineering investigation
logError(error, {
tags: { component: 'api', action: currentAction },
context: { userId, email, endpoint: url }
})
}
Important: Never display raw 500 error messages to users. They may contain stack traces, internal paths, or other sensitive information. Always show a generic user-friendly message.
WWAI uses Sentry for production error tracking. Understanding the logging configuration helps when debugging reported errors.
process.env.NODE_ENV !== 'production' skips logging in development)logError(error, { tags: { component, action }, context: { userId, email } })The following errors are filtered out as non-actionable (typically caused by client network conditions or browser quirks):
| Ignored Pattern | Reason |
|---|---|
networkerror |
Client lost internet connectivity |
network error |
Same as above (case variant) |
failed to fetch |
Fetch API network failure |
load failed |
Resource loading failure |
hydration failed |
Next.js SSR/CSR mismatch (cosmetic, not functional) |
hydration error |
Same as above (variant) |
script error. |
Cross-origin script error with no useful info |
resizeobserver |
ResizeObserver loop limit exceeded (browser quirk, harmless) |
For frontend developers implementing new API integrations, use this comprehensive extraction pattern that handles all known WWAI error response formats:
/**
* Extract error message from any WWAI API error response.
* Handles all known response formats: detail, message, error, and field-level errors.
*/
function extractErrorMessage(error: AxiosError, fallback: string): string {
const data = error.response?.data as Record<string, any> | undefined
if (!data) return fallback
// Check standard fields in priority order
if (typeof data.detail === 'string') return data.detail
if (typeof data.message === 'string') return data.message
if (typeof data.error === 'string') return data.error
// Check field-level validation errors (e.g., registration)
// These come as { fieldName: ["error message", ...] }
for (const key of Object.keys(data)) {
if (Array.isArray(data[key]) && data[key].length > 0) {
return data[key][0]
}
}
return fallback
}
API Error Received
|
+-- Status 400
| +-- Check for field-level errors (email[], password[], etc.)
| +-- Display specific validation message to user
|
+-- Status 401
| +-- Attempt token refresh (POST /api/user/refresh/)
| +-- If refresh fails -> redirect to login
| +-- If refresh succeeds -> retry original request
|
+-- Status 403
| +-- Check detail value:
| +-- "Your subscription is paused" -> show pause/resume modal
| +-- "word_limit" -> show word limit warning
| +-- "Your content exceeds the limit of your plan" -> show upgrade
| +-- "You have not enough credits" -> show credits modal
| +-- Other -> show generic upgrade prompt
|
+-- Status 404
| +-- Verify trailing slash in URL
| +-- Check if resource was deleted
| +-- Show "not found" message
|
+-- Status 429
| +-- Show rate limit / usage limit message
| +-- Suggest plan upgrade
|
+-- Status 500
+-- Show generic error to user
+-- Log to Sentry with context
| Date | Author | Change |
|---|---|---|
| 2026-01-30 | Admin | Rewrote page with WWAI-specific error responses, per-status documentation, extraction patterns, and Sentry configuration |
| 2026-01-30 | Admin | Initial creation |
Prev: API - Payment Endpoints | Up: WalterWrites