Files
goodgo-platform/docs/api-error-codes.md
Ho Ngoc Hai f5ef9d8c86 docs: add comprehensive API error codes reference for frontend consumption
Document all 33 structured errorCode values from DomainException/ErrorCode
enum across all modules (auth, user, listing, property, media, payment,
subscription, course). Includes HTTP status mapping, Vietnamese error
messages, usage examples per module, alphabetical quick-reference table,
and TypeScript integration guide for frontend error handling.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-10 20:11:12 +07:00

14 KiB

API Error Codes Reference

All GoodGo Platform API errors follow a structured JSON format. This document lists every errorCode value, its HTTP status, description, and usage examples.

Error Response Format

Every error response from the API has this shape:

{
  "statusCode": 400,
  "errorCode": "VALIDATION_FAILED",
  "message": "Human-readable error description",
  "details": { },
  "correlationId": "optional-uuid",
  "timestamp": "2026-04-10T12:00:00.000Z"
}
Field Type Description
statusCode number HTTP status code (e.g. 400, 401, 404).
errorCode string Machine-readable error identifier (see tables below).
message string Human-readable explanation (Vietnamese for business rules, English for system errors).
details object Optional structured metadata (e.g. validation field names, current/target status).
correlationId string Echoed from the X-Correlation-Id request header when present.
timestamp string ISO 8601 timestamp of the error occurrence.

General Error Codes

These codes apply across all modules. They are also used as fallback codes when a plain HttpException (without a domain error code) is thrown.

Error Code HTTP Status Description When It Occurs
INTERNAL_ERROR 500 Unexpected server error. Unhandled exceptions, infrastructure failures.
VALIDATION_FAILED 400 Request data failed validation. Invalid input fields, business rule violations (e.g. invalid phone format, bad status transitions).
NOT_FOUND 404 Requested resource does not exist. Entity lookup by ID returns no result.
CONFLICT 409 Resource state conflict. Duplicate creation, conflicting state transition.
UNAUTHORIZED 401 Authentication required or failed. Missing/invalid/expired auth token, bad credentials.
FORBIDDEN 403 Insufficient permissions. Role-based access denial, CSRF token failure, resource ownership mismatch.
BAD_REQUEST 400 Malformed request. Invalid file upload, unsupported content type, bad query parameters.
TOO_MANY_REQUESTS 429 Rate limit exceeded. Too many API requests in a short period.

Auth Error Codes

Error Code HTTP Status Description When It Occurs
AUTH_INVALID_CREDENTIALS 401 Login credentials are incorrect. Phone number or password mismatch during local login.
AUTH_TOKEN_EXPIRED 401 JWT token has expired. Access token or refresh token past expiration.
AUTH_TOKEN_INVALID 401 JWT token is malformed or tampered with. Token signature verification failure.
AUTH_INSUFFICIENT_PERMISSIONS 403 Authenticated user lacks required role/permission. Non-admin accessing admin endpoints, role mismatch.

Auth Module Usage Examples

// POST /auth/login — wrong password
{
  "statusCode": 401,
  "errorCode": "UNAUTHORIZED",
  "message": "Số điện thoại hoặc mật khẩu không đúng"
}

// POST /auth/refresh — expired refresh token
{
  "statusCode": 401,
  "errorCode": "UNAUTHORIZED",
  "message": "Refresh token không hợp lệ hoặc đã hết hạn"
}

// POST /auth/register — phone already registered
{
  "statusCode": 409,
  "errorCode": "CONFLICT",
  "message": "Số điện thoại đã được đăng ký"
}

// POST /auth/register — invalid phone format
{
  "statusCode": 400,
  "errorCode": "VALIDATION_FAILED",
  "message": "Số điện thoại không hợp lệ. Yêu cầu 10 chữ số bắt đầu bằng 0"
}

User Error Codes

Error Code HTTP Status Description When It Occurs
USER_NOT_FOUND 404 User does not exist. Profile lookup, admin user management.
USER_ALREADY_EXISTS 409 User with given identifier already exists. Duplicate registration (phone or email).
USER_INVALID_PHONE 400 Phone number format is invalid. Registration or profile update with bad phone format.

User Module Usage Examples

// GET /auth/profile — deleted or nonexistent user
{
  "statusCode": 404,
  "errorCode": "NOT_FOUND",
  "message": "Người dùng with id '...' not found"
}

// DELETE /auth/account — account already deleted
{
  "statusCode": 400,
  "errorCode": "VALIDATION_FAILED",
  "message": "Tài khoản đã bị xóa"
}

Listing Error Codes

Error Code HTTP Status Description When It Occurs
LISTING_NOT_FOUND 404 Listing does not exist. Lookup by ID for viewing, moderation, or status change.
LISTING_INVALID_STATUS_TRANSITION 400 Status transition is not allowed. Attempting an invalid workflow step (e.g. EXPIREDACTIVE).
LISTING_ALREADY_ACTIVE 409 Listing is already in active state. Re-activating a listing that is already published.
LISTING_EXPIRED 400 Listing has expired. Attempting operations on an expired listing.

Listing Module Usage Examples

// PATCH /listings/:id/status — invalid transition
{
  "statusCode": 400,
  "errorCode": "VALIDATION_FAILED",
  "message": "Không thể chuyển trạng thái từ EXPIRED sang ACTIVE",
  "details": {
    "currentStatus": "EXPIRED",
    "targetStatus": "ACTIVE"
  }
}

// POST /admin/listings/:id/approve — already approved
{
  "statusCode": 400,
  "errorCode": "VALIDATION_FAILED",
  "message": "Listing này không ở trạng thái chờ duyệt"
}

// POST /listings — invalid address
{
  "statusCode": 400,
  "errorCode": "VALIDATION_FAILED",
  "message": "Địa chỉ không hợp lệ"
}

Property Error Codes

Error Code HTTP Status Description When It Occurs
PROPERTY_NOT_FOUND 404 Property does not exist. Property lookup for media upload or listing creation.

Property Module Usage Examples

// POST /properties/:id/media — property not found
{
  "statusCode": 404,
  "errorCode": "NOT_FOUND",
  "message": "Property with id '...' not found"
}

// POST /properties/:id/media — too many files
{
  "statusCode": 400,
  "errorCode": "VALIDATION_FAILED",
  "message": "Tối đa 30 ảnh/video cho mỗi bất động sản"
}

Media Error Codes

Error Code HTTP Status Description When It Occurs
MEDIA_UPLOAD_FAILED 500 File upload to storage provider failed. S3/cloud storage write failure.
MEDIA_LIMIT_EXCEEDED 400 Maximum number of media files reached. Exceeding per-property media limit.

Payment Error Codes

Error Code HTTP Status Description When It Occurs
PAYMENT_FAILED 500 Payment processing failed. Gateway error, provider timeout, payment link creation failure.
PAYMENT_ALREADY_PROCESSED 409 Payment has already been completed, failed, or refunded. Duplicate callback, retry on non-pending payment, refunding a non-completed payment.
PAYMENT_INVALID_AMOUNT 400 Payment amount is invalid. Zero/negative amount, amount below minimum threshold.

Payment Module Usage Examples

// POST /payments — duplicate idempotency key
{
  "statusCode": 409,
  "errorCode": "CONFLICT",
  "message": "Thanh toán với idempotency key này đã tồn tại"
}

// POST /payments/callback — invalid signature
{
  "statusCode": 400,
  "errorCode": "VALIDATION_FAILED",
  "message": "Chữ ký callback không hợp lệ"
}

// POST /payments/:id/refund — payment not completed
{
  "statusCode": 400,
  "errorCode": "VALIDATION_FAILED",
  "message": "Chỉ có thể hoàn tiền cho thanh toán đã hoàn tất"
}

// Entity-level: marking already-processed payment as completed
{
  "statusCode": 409,
  "errorCode": "PAYMENT_ALREADY_PROCESSED",
  "message": "Cannot complete payment in status COMPLETED"
}

Subscription Error Codes

Error Code HTTP Status Description When It Occurs
SUBSCRIPTION_NOT_FOUND 404 Subscription does not exist. Lookup for cancellation, upgrade, or usage metering.
SUBSCRIPTION_ALREADY_ACTIVE 409 User already has an active subscription. Attempting to create a second active subscription.
SUBSCRIPTION_ALREADY_CANCELLED 409 Subscription has already been cancelled. Re-cancelling a subscription.
SUBSCRIPTION_INACTIVE 409 Subscription is not in an active state. Upgrading, expiring, or marking past-due on a non-active subscription.
QUOTA_EXCEEDED 403 Usage quota for the current plan has been exceeded. Exceeding listing, lead, or search quota limits.

Subscription Module Usage Examples

// POST /subscriptions — already has active subscription
{
  "statusCode": 409,
  "errorCode": "CONFLICT",
  "message": "Người dùng đã có subscription đang hoạt động"
}

// POST /subscriptions/cancel — already cancelled
{
  "statusCode": 400,
  "errorCode": "VALIDATION_FAILED",
  "message": "Subscription đã bị hủy trước đó"
}

// POST /subscriptions/upgrade — subscription not active
{
  "statusCode": 400,
  "errorCode": "VALIDATION_FAILED",
  "message": "Subscription không ở trạng thái hoạt động"
}

// Entity-level: upgrade on inactive subscription
{
  "statusCode": 409,
  "errorCode": "SUBSCRIPTION_INACTIVE",
  "message": "Không thể nâng cấp subscription ở trạng thái CANCELLED"
}

// Quota guard — plan limit exceeded
{
  "statusCode": 403,
  "errorCode": "FORBIDDEN",
  "message": "Bạn đã đạt giới hạn listings. Vui lòng nâng cấp gói để tiếp tục."
}

Course Error Codes

Error Code HTTP Status Description When It Occurs
COURSE_NOT_FOUND 404 Course does not exist. Course lookup by ID.
COURSE_ALREADY_PUBLISHED 409 Course is already in published state. Re-publishing an active course.
COURSE_ENROLLMENT_CLOSED 400 Course enrollment period has ended. Attempting to enroll after the cutoff date.

Error Code Quick-Reference (Sorted Alphabetically)

Error Code HTTP Status Module
AUTH_INSUFFICIENT_PERMISSIONS 403 Auth
AUTH_INVALID_CREDENTIALS 401 Auth
AUTH_TOKEN_EXPIRED 401 Auth
AUTH_TOKEN_INVALID 401 Auth
BAD_REQUEST 400 General
CONFLICT 409 General
COURSE_ALREADY_PUBLISHED 409 Course
COURSE_ENROLLMENT_CLOSED 400 Course
COURSE_NOT_FOUND 404 Course
FORBIDDEN 403 General
INTERNAL_ERROR 500 General
LISTING_ALREADY_ACTIVE 409 Listing
LISTING_EXPIRED 400 Listing
LISTING_INVALID_STATUS_TRANSITION 400 Listing
LISTING_NOT_FOUND 404 Listing
MEDIA_LIMIT_EXCEEDED 400 Media
MEDIA_UPLOAD_FAILED 500 Media
NOT_FOUND 404 General
PAYMENT_ALREADY_PROCESSED 409 Payment
PAYMENT_FAILED 500 Payment
PAYMENT_INVALID_AMOUNT 400 Payment
PROPERTY_NOT_FOUND 404 Property
QUOTA_EXCEEDED 403 Subscription
SUBSCRIPTION_ALREADY_ACTIVE 409 Subscription
SUBSCRIPTION_ALREADY_CANCELLED 409 Subscription
SUBSCRIPTION_INACTIVE 409 Subscription
SUBSCRIPTION_NOT_FOUND 404 Subscription
TOO_MANY_REQUESTS 429 General
UNAUTHORIZED 401 General
USER_ALREADY_EXISTS 409 User
USER_INVALID_PHONE 400 User
USER_NOT_FOUND 404 User

Frontend Integration Guide

TypeScript Error Type

interface ApiError {
  statusCode: number;
  errorCode: string;
  message: string;
  details?: Record<string, unknown>;
  correlationId?: string;
  timestamp: string;
}

Handling Errors by Code

import axios, { AxiosError } from 'axios';

async function handleApiCall() {
  try {
    const res = await axios.post('/api/listings', payload);
    return res.data;
  } catch (err) {
    if (axios.isAxiosError(err)) {
      const apiError = err.response?.data as ApiError;

      switch (apiError.errorCode) {
        case 'VALIDATION_FAILED':
          // Show field-level errors from apiError.details
          showValidationErrors(apiError.details);
          break;
        case 'UNAUTHORIZED':
        case 'AUTH_TOKEN_EXPIRED':
          // Redirect to login or refresh token
          await refreshSession();
          break;
        case 'QUOTA_EXCEEDED':
          // Show upgrade prompt
          showUpgradeDialog(apiError.message);
          break;
        case 'CONFLICT':
          // Inform user of duplicate action
          showToast(apiError.message, 'warning');
          break;
        case 'NOT_FOUND':
          // Navigate to 404 page or show missing resource message
          router.push('/404');
          break;
        default:
          showToast('Đã xảy ra lỗi, vui lòng thử lại', 'error');
      }
    }
  }
}

Error Code Constants (Optional)

For type safety, maintain error codes as a union type on the frontend:

type ErrorCode =
  | 'INTERNAL_ERROR'
  | 'VALIDATION_FAILED'
  | 'NOT_FOUND'
  | 'CONFLICT'
  | 'UNAUTHORIZED'
  | 'FORBIDDEN'
  | 'BAD_REQUEST'
  | 'TOO_MANY_REQUESTS'
  | 'AUTH_INVALID_CREDENTIALS'
  | 'AUTH_TOKEN_EXPIRED'
  | 'AUTH_TOKEN_INVALID'
  | 'AUTH_INSUFFICIENT_PERMISSIONS'
  | 'USER_NOT_FOUND'
  | 'USER_ALREADY_EXISTS'
  | 'USER_INVALID_PHONE'
  | 'COURSE_NOT_FOUND'
  | 'COURSE_ALREADY_PUBLISHED'
  | 'COURSE_ENROLLMENT_CLOSED'
  | 'LISTING_NOT_FOUND'
  | 'LISTING_INVALID_STATUS_TRANSITION'
  | 'LISTING_ALREADY_ACTIVE'
  | 'LISTING_EXPIRED'
  | 'PROPERTY_NOT_FOUND'
  | 'MEDIA_UPLOAD_FAILED'
  | 'MEDIA_LIMIT_EXCEEDED'
  | 'PAYMENT_FAILED'
  | 'PAYMENT_ALREADY_PROCESSED'
  | 'PAYMENT_INVALID_AMOUNT'
  | 'SUBSCRIPTION_NOT_FOUND'
  | 'SUBSCRIPTION_ALREADY_ACTIVE'
  | 'SUBSCRIPTION_ALREADY_CANCELLED'
  | 'SUBSCRIPTION_INACTIVE'
  | 'QUOTA_EXCEEDED';

Source Files

  • Error code enum: apps/api/src/modules/shared/domain/error-codes.ts
  • Domain exceptions: apps/api/src/modules/shared/domain/domain-exception.ts
  • Global exception filter: apps/api/src/modules/shared/infrastructure/filters/global-exception.filter.ts