Documentation is currently in beta. Report issues →
API ReferenceError Codes

Error Codes

Standard error codes and responses used across Y3NKO APIs.

Error Response Format

All API errors follow a consistent format:

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "details": {
      // Optional additional context
    }
  }
}

HTTP Status Codes

StatusMeaning
200Success
201Created
400Bad Request - Invalid input
401Unauthorized - Authentication required
403Forbidden - Insufficient permissions
404Not Found - Resource doesn’t exist
409Conflict - Resource conflict
422Unprocessable Entity - Validation failed
429Too Many Requests - Rate limited
500Internal Server Error

Error Codes Reference

Authentication Errors (401)

CodeMessageDescription
UNAUTHORIZEDAuthentication requiredNo valid session found
SESSION_EXPIREDYour session has expiredToken needs refresh
INVALID_CREDENTIALSInvalid email or passwordLogin failed
EMAIL_NOT_CONFIRMEDPlease verify your emailEmail not confirmed

Authorization Errors (403)

CodeMessageDescription
FORBIDDENYou don’t have permissionRole insufficient
NOT_YOUR_RESOURCEThis doesn’t belong to youAccessing another user’s data
ROLE_REQUIREDAdmin access requiredAdmin role needed

Validation Errors (400/422)

CodeMessageDescription
VALIDATION_ERRORInvalid input dataZod validation failed
INVALID_DATEInvalid date formatDate parsing failed
INVALID_PHONEInvalid phone numberPhone format wrong
INVALID_EMAILInvalid email addressEmail format wrong
MISSING_REQUIREDRequired field missingRequired field not provided

Validation Error Details:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input data",
    "details": {
      "issues": [
        {
          "path": ["guestPhone"],
          "message": "Phone must start with +233",
          "code": "invalid_string"
        },
        {
          "path": ["checkOut"],
          "message": "Check-out must be after check-in",
          "code": "custom"
        }
      ]
    }
  }
}

Resource Errors (404/409)

CodeMessageDescription
NOT_FOUNDResource not foundRequested item doesn’t exist
LISTING_NOT_FOUNDListing not foundListing doesn’t exist or unpublished
BOOKING_NOT_FOUNDBooking not foundBooking reference invalid
USER_NOT_FOUNDUser not foundUser doesn’t exist
DATES_UNAVAILABLESelected dates unavailableDates already booked
ALREADY_EXISTSResource already existsDuplicate creation attempt
ALREADY_CANCELLEDBooking already cancelledCan’t cancel twice
ALREADY_PAIDPayment already completedDuplicate payment attempt

Payment Errors

CodeMessageDescription
PAYMENT_FAILEDPayment failedPaystack returned error
PAYMENT_DECLINEDPayment was declinedCard/MoMo declined
INSUFFICIENT_FUNDSInsufficient fundsNot enough balance
INVALID_CARDInvalid card detailsCard validation failed
REFUND_FAILEDRefund processing failedRefund couldn’t be processed

Rate Limiting (429)

CodeMessageDescription
RATE_LIMITEDToo many requestsRate limit exceeded

Rate Limit Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1709912345
Retry-After: 60

Server Errors (500)

CodeMessageDescription
INTERNAL_ERRORSomething went wrongUnexpected server error
DATABASE_ERRORDatabase errorDB query failed
EXTERNAL_SERVICE_ERRORExternal service errorPaystack/Cloudinary/Resend failed

Handling Errors

Client-Side

async function createBooking(data: BookingInput) {
  try {
    const response = await fetch('/api/bookings', {
      method: 'POST',
      body: JSON.stringify(data)
    })
 
    if (!response.ok) {
      const error = await response.json()
 
      switch (error.error.code) {
        case 'DATES_UNAVAILABLE':
          toast.error('These dates are no longer available')
          break
        case 'VALIDATION_ERROR':
          // Show field-specific errors
          error.error.details.issues.forEach(issue => {
            setFieldError(issue.path[0], issue.message)
          })
          break
        case 'UNAUTHORIZED':
          router.push('/login')
          break
        default:
          toast.error(error.error.message)
      }
      return null
    }
 
    return response.json()
  } catch (e) {
    toast.error('Network error. Please try again.')
    return null
  }
}

Server-Side

// lib/errors.ts
export class AppError extends Error {
  constructor(
    public code: string,
    public message: string,
    public status: number = 400,
    public details?: any
  ) {
    super(message)
  }
}
 
export function handleError(error: unknown) {
  if (error instanceof AppError) {
    return NextResponse.json(
      {
        error: {
          code: error.code,
          message: error.message,
          details: error.details
        }
      },
      { status: error.status }
    )
  }
 
  console.error('Unexpected error:', error)
 
  return NextResponse.json(
    {
      error: {
        code: 'INTERNAL_ERROR',
        message: 'Something went wrong'
      }
    },
    { status: 500 }
  )
}

Usage in API Routes

// app/api/bookings/route.ts
import { AppError, handleError } from '@/lib/errors'
 
export async function POST(request: Request) {
  try {
    const data = await request.json()
 
    // Validation
    const result = bookingSchema.safeParse(data)
    if (!result.success) {
      throw new AppError(
        'VALIDATION_ERROR',
        'Invalid input data',
        400,
        { issues: result.error.issues }
      )
    }
 
    // Check availability
    const available = await checkAvailability(data.listingId, data.checkIn, data.checkOut)
    if (!available) {
      throw new AppError(
        'DATES_UNAVAILABLE',
        'Selected dates are no longer available',
        409
      )
    }
 
    // Create booking...
 
  } catch (error) {
    return handleError(error)
  }
}

Logging

Errors are logged server-side with context:

console.error({
  code: error.code,
  message: error.message,
  userId: session?.user?.id,
  path: request.url,
  timestamp: new Date().toISOString()
})

In production, consider using a proper logging service (Sentry, LogRocket, etc.).