Error Handling
Fumai Mobile API menggunakan kode status HTTP standar dan menyertakan error code di response body.
HTTP Status Codes
| Status Code | Keterangan |
|---|---|
200 | OK — Request berhasil |
201 | Created — Resource berhasil dibuat |
400 | Bad Request — Validasi gagal |
401 | Unauthorized — Token tidak valid atau tidak disertakan |
403 | Forbidden — Tidak memiliki akses |
404 | Not Found — Resource tidak ditemukan |
409 | Conflict — Data duplikat atau konflik |
429 | Too Many Requests — Rate limit terlampaui |
500 | Internal Server Error — Kesalahan server |
Error Codes
| Error Code | HTTP Status | Keterangan | Aksi di Client |
|---|---|---|---|
UNAUTHORIZED | 401 | Token tidak ada | Redirect ke login |
INVALID_ACCESS_TOKEN | 401 | Access token expired/invalid | Panggil /auth/refresh |
INVALID_CREDENTIALS | 401 | Email atau password salah | Tampilkan field error |
GOOGLE_ACCOUNT | 401 | User terdaftar via Google | Arahkan ke Google Sign In |
EMAIL_NOT_VERIFIED | 403 | Email belum diverifikasi | Arahkan ke verify step |
INVALID_REFRESH_TOKEN | 401 | Refresh token invalid/expired | Redirect ke login |
REFRESH_FAILED | 401 | Gagal memperbarui token | Redirect ke login |
VALIDATION_ERROR | 400 | Input tidak valid | Tampilkan field errors |
INVALID_EMAIL | 400 | Format email tidak valid | Tampilkan field error |
EMAIL_EXISTS | 409 | Email sudah terdaftar | Arahkan ke login |
INVALID_CODE | 400 | OTP code salah/expired | Tampilkan error, sarankan resend |
EMAIL_FAILED | 500 | Gagal mengirim email | Retry nanti |
USER_NOT_FOUND | 404 | User tidak ada | Redirect ke login |
INVALID_GOOGLE_TOKEN | 401 | Google token invalid | Retry Google sign-in |
GOOGLE_EMAIL_NOT_VERIFIED | 401 | Email Google belum verified | Tampilkan error |
INVALID_APPLE_TOKEN | 401 | Apple token invalid | Retry Apple sign-in |
APPLE_EMAIL_REQUIRED | 400 | Email tidak tersedia dari Apple | User harus berikan izin email |
ACCOUNT_NOT_VERIFIED | 401 | Email belum verified (OAuth linking) | Tampilkan prompt verifikasi |
PASSWORD_ALREADY_SET | 409 | User sudah punya password | Arahkan ke change password |
INVALID_TOKEN | 400 | Reset token invalid/expired | Tampilkan error, resend link |
INVALID_FILE_TYPE | 400 | File bukan image | Tampilkan error |
FILE_TOO_LARGE | 400 | File lebih dari 5MB | Tampilkan error |
SUBSCRIPTION_REQUIRED | 403 | Fitur premium | Tampilkan upgrade prompt |
RATE_LIMITED | 429 | Terlalu banyak request | Retry setelah delay |
SERVER_ERROR | 500 | Internal server error | Retry atau tampilkan error |
Contoh Penanganan Error
async function callAPI(endpoint, options = {}) {
const res = await fetch(`https://fumai.app/api/mobile/v1${endpoint}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
...options,
})
const result = await res.json()
if (!result.success) {
switch (result.code) {
case 'INVALID_ACCESS_TOKEN':
// Token expired, coba refresh
await refreshAccessToken()
return callAPI(endpoint, options)
case 'RATE_LIMITED':
// Tunggu sebelum retry
const retryAfter = res.headers.get('Retry-After')
await sleep(retryAfter * 1000)
return callAPI(endpoint, options)
case 'VALIDATION_ERROR':
console.error('Validasi gagal:', result.error)
break
default:
console.error('API Error:', result.code, result.error)
}
throw new Error(result.error)
}
return result
}Rate Limits
| Endpoint | Limit | Window |
|---|---|---|
| Check Email | 10 | 1 menit |
| Login | 5 | 1 menit |
| Register | 3 | 1 jam |
| Google OAuth | 10 | 15 menit |
| Apple Sign In | 10 | 15 menit |
| Verify Email | 10 | 15 menit |
| Resend Verification | 2 | 5 menit |
| Forgot Password | 3 | 1 jam |
| Reset Password | 5 | 15 menit |
| Report Issue | 3 | 1 jam |
| Sessions | 3 | 1 jam |
| Set Initial Password | 3 | 1 jam |
| Refresh Token | 30 | 1 menit |
| General API | 100 | 1 menit |
Response header saat rate limited:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
Retry-After: 30Retry Strategy
Info: Untuk error
429(Rate Limit) dan5xx(Server Error), disarankan menggunakan exponential backoff untuk retry.
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const res = await fetch(url, options)
if (res.ok) return res.json()
if (res.status === 429 || res.status >= 500) {
const delay = Math.pow(2, attempt) * 1000
await new Promise(resolve => setTimeout(resolve, delay))
continue
}
return res.json()
}
throw new Error('Max retries exceeded')
}Last updated on