{"openapi":"3.0.3","info":{"title":"The Hub API","version":"1.0.0","description":"REST API for The Hub - a multi-vendor e-commerce marketplace for the Kenyan market. Handles authentication, user management, merchant onboarding, product listings, orders, and MPESA payments.","contact":{"name":"The Hub","email":"support@thehub.co.ke"}},"servers":[{"url":"http://localhost:3000","description":"Local development"},{"url":"https://api.thehub.co.ke","description":"Production"}],"tags":[{"name":"Authentication","description":"Register, login, token management, email verification, password reset, and 2FA"},{"name":"Users","description":"User profile management"},{"name":"Merchants","description":"Merchant applications and shop management"},{"name":"Products","description":"Product listings"},{"name":"Orders","description":"Order management and tracking"},{"name":"Payments","description":"MPESA payment integration"},{"name":"Admin","description":"Platform administration"}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Access token issued on login or email verification. Valid for 15 minutes."}},"schemas":{"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string"},"details":{"type":"array","items":{"type":"object"},"nullable":true}}},"UserProfile":{"type":"object","properties":{"id":{"type":"string","example":"clxyz123"},"name":{"type":"string","example":"Jane Doe"},"email":{"type":"string","example":"jane@example.com"},"role":{"type":"string","enum":["USER","MERCHANT","ADMIN"]}}},"TokenPair":{"type":"object","properties":{"accessToken":{"type":"string","description":"JWT access token (15m TTL)"},"refreshToken":{"type":"string","description":"Opaque refresh token (7d TTL)"},"user":{"$ref":"#/components/schemas/UserProfile"}}},"RegisterRequest":{"type":"object","required":["name","email","password"],"properties":{"name":{"type":"string","minLength":2,"maxLength":80,"example":"Jane Doe"},"email":{"type":"string","format":"email","example":"jane@example.com"},"password":{"type":"string","minLength":8,"maxLength":128,"example":"Sup3rS3cure!"},"phone":{"type":"string","example":"+254712345678","nullable":true}}},"LoginRequest":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email","example":"jane@example.com"},"password":{"type":"string","example":"Sup3rS3cure!"}}},"EmailCodeRequest":{"type":"object","required":["email","code"],"properties":{"email":{"type":"string","format":"email"},"code":{"type":"string","minLength":6,"maxLength":6,"example":"482910"}}},"EmailRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}},"ResetPasswordRequest":{"type":"object","required":["email","code","newPassword"],"properties":{"email":{"type":"string","format":"email"},"code":{"type":"string","minLength":6,"maxLength":6},"newPassword":{"type":"string","minLength":8,"maxLength":128}}},"ChangePasswordRequest":{"type":"object","required":["currentPassword","newPassword"],"properties":{"currentPassword":{"type":"string"},"newPassword":{"type":"string","minLength":8,"maxLength":128}}},"RefreshRequest":{"type":"object","required":["refreshToken"],"properties":{"refreshToken":{"type":"string"}}},"LogoutRequest":{"type":"object","required":["refreshToken"],"properties":{"refreshToken":{"type":"string"}}},"TwoFactorVerifyRequest":{"type":"object","required":["code"],"properties":{"code":{"type":"string","minLength":6,"maxLength":6,"example":"291047"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ValidationError":{"description":"Request body failed validation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"TooManyRequests":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/api/auth/register":{"post":{"tags":["Authentication"],"summary":"Register","description":"Create a new user account. A 6-digit verification code is sent to the provided email. The account cannot be used until the email is verified via `/api/auth/verify-email`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}}},"responses":{"201":{"description":"Account created - verification code sent","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"properties":{"message":{"type":"string"}}}}}]}}}},"409":{"description":"Email already in use","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/auth/verify-email":{"post":{"tags":["Authentication"],"summary":"Verify email","description":"Submit the 6-digit OTP code sent to the user's email after registration. On success, returns an access + refresh token pair and activates the account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCodeRequest"}}}},"responses":{"200":{"description":"Email verified - tokens issued","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"$ref":"#/components/schemas/TokenPair"}}}]}}}},"400":{"description":"Invalid or expired code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/auth/login":{"post":{"tags":["Authentication"],"summary":"Login","description":"Authenticate with email and password. Returns an access token (15m) and a refresh token (7d). The email must be verified before login.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"Login successful","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"$ref":"#/components/schemas/TokenPair"}}}]}}}},"401":{"description":"Invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Email not verified or account suspended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/auth/logout":{"post":{"tags":["Authentication"],"summary":"Logout","description":"Revoke the provided refresh token. The access token will expire naturally (15m TTL).","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutRequest"}}}},"responses":{"200":{"description":"Logged out successfully"},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/auth/refresh":{"post":{"tags":["Authentication"],"summary":"Refresh tokens","description":"Exchange a valid refresh token for a new access + refresh token pair. The old refresh token is revoked immediately (rotation). If you replay a revoked token, it will be rejected.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshRequest"}}}},"responses":{"200":{"description":"New token pair issued","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"properties":{"accessToken":{"type":"string"},"refreshToken":{"type":"string"}}}}}]}}}},"401":{"description":"Token invalid, expired, or revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/auth/resend-verification":{"post":{"tags":["Authentication"],"summary":"Resend verification code","description":"Resend the email verification code. Previous codes are invalidated. Always returns a success message to prevent email enumeration.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailRequest"}}}},"responses":{"200":{"description":"Code sent (or silently ignored if email is already verified)"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/auth/forgot-password":{"post":{"tags":["Authentication"],"summary":"Forgot password","description":"Send a 6-digit password reset code to the user's email. Always returns the same message to prevent email enumeration.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailRequest"}}}},"responses":{"200":{"description":"Reset code sent (or silently ignored if email not found)"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/auth/reset-password":{"post":{"tags":["Authentication"],"summary":"Reset password","description":"Reset the user's password using the 6-digit OTP code. On success, all active refresh tokens are revoked (forces re-login on all devices).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordRequest"}}}},"responses":{"200":{"description":"Password reset successfully"},"400":{"description":"Invalid or expired code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/auth/change-password":{"post":{"tags":["Authentication"],"summary":"Change password","description":"Change the authenticated user's password. Requires the current password for confirmation.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}}},"responses":{"200":{"description":"Password changed successfully"},"400":{"description":"Current password incorrect","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/auth/2fa/send":{"post":{"tags":["Authentication"],"summary":"Send 2FA code","description":"Send a 6-digit two-factor authentication code to the authenticated user's email. Previous unused codes are invalidated.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"2FA code sent"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/auth/2fa/verify":{"post":{"tags":["Authentication"],"summary":"Verify 2FA code","description":"Verify the 6-digit two-factor authentication code. The code has a 10-minute TTL and a maximum of 5 attempts.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwoFactorVerifyRequest"}}}},"responses":{"200":{"description":"2FA verified","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"properties":{"data":{"properties":{"message":{"type":"string"},"verified":{"type":"boolean"}}}}}]}}}},"400":{"description":"Invalid or expired code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}}}}