Skip to main content

Error Codes

This page documents all error codes returned by the FireBackup API. Understanding these errors helps you handle failures gracefully and provide meaningful feedback to users.

Error Response Format

All API errors follow a consistent JSON format:

{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": {
// Optional additional context
}
}
}
FieldTypeDescription
successbooleanAlways false for errors
error.codestringMachine-readable error code
error.messagestringHuman-readable description
error.detailsobjectAdditional context (optional)

HTTP Status Codes

StatusDescriptionCommon Causes
400Bad RequestInvalid input, validation errors
401UnauthorizedMissing or invalid token
403ForbiddenInsufficient permissions
404Not FoundResource doesn't exist
409ConflictDuplicate resource, state conflict
422Unprocessable EntityValid JSON but invalid semantics
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side failure
502Bad GatewayUpstream service failure
503Service UnavailableMaintenance or overload

Authentication Errors

UNAUTHORIZED

Status: 401

{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
}

Causes:

  • Missing Authorization header
  • Token not provided

Resolution: Include a valid Bearer token in the Authorization header.


INVALID_TOKEN

Status: 401

{
"success": false,
"error": {
"code": "INVALID_TOKEN",
"message": "Invalid or malformed token"
}
}

Causes:

  • Malformed JWT
  • Invalid signature
  • Token from different issuer

Resolution: Obtain a new token through the authentication flow.


TOKEN_EXPIRED

Status: 401

{
"success": false,
"error": {
"code": "TOKEN_EXPIRED",
"message": "Token has expired",
"details": {
"expiredAt": "2024-01-15T10:00:00Z"
}
}
}

Causes:

  • JWT past expiration time
  • Session timeout

Resolution: Refresh the token or re-authenticate.


INVALID_REFRESH_TOKEN

Status: 401

{
"success": false,
"error": {
"code": "INVALID_REFRESH_TOKEN",
"message": "Invalid or expired refresh token"
}
}

Causes:

  • Refresh token revoked
  • Refresh token expired
  • Token already used (one-time use)

Resolution: Re-authenticate through the full OAuth flow.

Authorization Errors

PERMISSION_DENIED

Status: 403

{
"success": false,
"error": {
"code": "PERMISSION_DENIED",
"message": "You don't have permission to perform this action",
"details": {
"requiredRole": "admin",
"currentRole": "member",
"requiredPermission": "backups:delete"
}
}
}

Causes:

  • User role insufficient for operation
  • Resource belongs to another organization

Resolution: Request elevated permissions from an admin.


ORGANIZATION_ACCESS_DENIED

Status: 403

{
"success": false,
"error": {
"code": "ORGANIZATION_ACCESS_DENIED",
"message": "You are not a member of this organization"
}
}

Causes:

  • User not in organization
  • Organization ID mismatch
  • Membership revoked

Resolution: Verify organization membership or use correct organization ID.


MISSING_ORGANIZATION_HEADER

Status: 400

{
"success": false,
"error": {
"code": "MISSING_ORGANIZATION_HEADER",
"message": "X-Organization-Id header is required"
}
}

Causes:

  • Header not provided
  • Empty header value

Resolution: Include X-Organization-Id header with valid organization ID.

Resource Errors

PROJECT_NOT_FOUND

Status: 404

{
"success": false,
"error": {
"code": "PROJECT_NOT_FOUND",
"message": "Project with ID 'proj_xxx' not found"
}
}

Causes:

  • Project doesn't exist
  • Project deleted
  • Wrong organization context

Resolution: Verify project ID and organization context.


BACKUP_NOT_FOUND

Status: 404

{
"success": false,
"error": {
"code": "BACKUP_NOT_FOUND",
"message": "Backup 'bkp_xxx' not found"
}
}

Causes:

  • Backup doesn't exist
  • Backup deleted
  • Wrong project context

Resolution: Verify backup ID exists for the specified project.


SCHEDULE_NOT_FOUND

Status: 404

{
"success": false,
"error": {
"code": "SCHEDULE_NOT_FOUND",
"message": "Schedule 'sched_xxx' not found"
}
}

STORAGE_NOT_FOUND

Status: 404

{
"success": false,
"error": {
"code": "STORAGE_NOT_FOUND",
"message": "Storage destination not found"
}
}

WEBHOOK_NOT_FOUND

Status: 404

{
"success": false,
"error": {
"code": "WEBHOOK_NOT_FOUND",
"message": "Webhook not found"
}
}

ORGANIZATION_NOT_FOUND

Status: 404

{
"success": false,
"error": {
"code": "ORGANIZATION_NOT_FOUND",
"message": "Organization not found"
}
}

PITR_NOT_ENABLED

Status: 400

{
"success": false,
"error": {
"code": "PITR_NOT_ENABLED",
"message": "PITR is not enabled for this project"
}
}

Resolution: Enable PITR using the /pitr/enable endpoint.

Validation Errors

VALIDATION_ERROR

Status: 400

{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": {
"errors": [
{
"field": "email",
"message": "Invalid email format"
},
{
"field": "name",
"message": "Name must be at least 2 characters"
}
]
}
}
}

Causes:

  • Missing required fields
  • Invalid field format
  • Value out of range

Resolution: Check the details.errors array for specific field issues.


INVALID_CRON

Status: 400

{
"success": false,
"error": {
"code": "INVALID_CRON",
"message": "Invalid CRON expression",
"details": {
"expression": "invalid * * * *",
"field": "minute",
"reason": "Expected number or special character"
}
}
}

Resolution: Use a valid 5-field CRON expression (minute hour day month weekday).


INVALID_TIMESTAMP

Status: 400

{
"success": false,
"error": {
"code": "INVALID_TIMESTAMP",
"message": "Requested timestamp is outside the retention window",
"details": {
"requestedTimestamp": "2024-01-01T00:00:00Z",
"oldestAvailable": "2024-01-08T10:00:00Z",
"latestAvailable": "2024-01-15T10:30:00Z"
}
}
}

Resolution: Use a timestamp within the PITR retention window.

Operation Errors

BACKUP_IN_PROGRESS

Status: 409

{
"success": false,
"error": {
"code": "BACKUP_IN_PROGRESS",
"message": "A backup is already in progress for this project",
"details": {
"activeBackupId": "bkp_abc123",
"startedAt": "2024-01-15T10:00:00Z"
}
}
}

Resolution: Wait for the current backup to complete or cancel it.


RESTORE_IN_PROGRESS

Status: 409

{
"success": false,
"error": {
"code": "RESTORE_IN_PROGRESS",
"message": "A restore operation is already in progress",
"details": {
"activeRestoreId": "restore_abc123"
}
}
}

RESTORE_FAILED

Status: 500

{
"success": false,
"error": {
"code": "RESTORE_FAILED",
"message": "Failed to restore backup",
"details": {
"reason": "Insufficient permissions on target project",
"documentsRestored": 5000,
"failedAt": "users/user_5001"
}
}
}

Resolution: Check target project permissions and retry.


SCHEDULE_CONFLICT

Status: 409

{
"success": false,
"error": {
"code": "SCHEDULE_CONFLICT",
"message": "A schedule already exists for this project at the same time"
}
}

Resolution: Use a different CRON expression or modify the existing schedule.

Firebase/GCP Errors

INVALID_CREDENTIALS

Status: 400

{
"success": false,
"error": {
"code": "INVALID_CREDENTIALS",
"message": "Failed to connect to Firebase project",
"details": {
"reason": "Token expired or revoked"
}
}
}

Resolution: Re-authenticate the Firebase project connection.


IAM_PERMISSION_DENIED

Status: 403

{
"success": false,
"error": {
"code": "IAM_PERMISSION_DENIED",
"message": "Missing required IAM permissions",
"details": {
"missing": [
"roles/datastore.importExportAdmin",
"roles/firebase.viewer"
]
}
}
}

Resolution: Grant the required IAM roles in Google Cloud Console.


AUDIT_LOGS_REQUIRED

Status: 400

{
"success": false,
"error": {
"code": "AUDIT_LOGS_REQUIRED",
"message": "Firebase audit logs must be enabled for PITR",
"details": {
"instructions": "Enable audit logs in Google Cloud Console"
}
}
}

Resolution: Enable Firestore audit logs in GCP IAM & Admin > Audit Logs.


FIREBASE_CONNECTION_FAILED

Status: 502

{
"success": false,
"error": {
"code": "FIREBASE_CONNECTION_FAILED",
"message": "Failed to connect to Firebase",
"details": {
"firebaseProjectId": "my-project",
"reason": "Network timeout"
}
}
}

Resolution: Check network connectivity and Firebase project status.

Storage Errors

BUCKET_NOT_FOUND

Status: 404

{
"success": false,
"error": {
"code": "BUCKET_NOT_FOUND",
"message": "Bucket 'my-bucket' does not exist or is not accessible"
}
}

STORAGE_ACCESS_DENIED

Status: 403

{
"success": false,
"error": {
"code": "STORAGE_ACCESS_DENIED",
"message": "Access denied to storage bucket",
"details": {
"bucket": "my-bucket",
"operation": "write"
}
}
}

Resolution: Verify storage credentials have read/write permissions.


FILE_NOT_ORPHAN

Status: 400

{
"success": false,
"error": {
"code": "FILE_NOT_ORPHAN",
"message": "File is associated with a backup and cannot be deleted directly"
}
}

Resolution: Delete the backup record first to orphan the file.


STORAGE_UPLOAD_FAILED

Status: 500

{
"success": false,
"error": {
"code": "STORAGE_UPLOAD_FAILED",
"message": "Failed to upload backup to storage",
"details": {
"storageType": "S3",
"bucket": "my-bucket",
"reason": "Request timeout"
}
}
}

Organization Errors

INVITATION_EXISTS

Status: 409

{
"success": false,
"error": {
"code": "INVITATION_EXISTS",
"message": "An invitation already exists for this email"
}
}

Resolution: Cancel the existing invitation or wait for it to expire.


INVITATION_EXPIRED

Status: 400

{
"success": false,
"error": {
"code": "INVITATION_EXPIRED",
"message": "This invitation has expired",
"details": {
"expiredAt": "2024-01-15T00:00:00Z"
}
}
}

Resolution: Request a new invitation from an organization admin.


CANNOT_REMOVE_OWNER

Status: 400

{
"success": false,
"error": {
"code": "CANNOT_REMOVE_OWNER",
"message": "Cannot remove the organization owner. Transfer ownership first."
}
}

Resolution: Transfer ownership to another member before leaving.


MEMBER_EXISTS

Status: 409

{
"success": false,
"error": {
"code": "MEMBER_EXISTS",
"message": "User is already a member of this organization"
}
}

LAST_ADMIN

Status: 400

{
"success": false,
"error": {
"code": "LAST_ADMIN",
"message": "Cannot remove the last admin. Promote another member first."
}
}

Rate Limiting

RATE_LIMIT_EXCEEDED

Status: 429

{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"details": {
"limit": 100,
"window": "1m",
"retryAfter": 45
}
}
}

Headers:

Retry-After: 45
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705312845

Resolution: Wait for retryAfter seconds before retrying.

Server Errors

INTERNAL_ERROR

Status: 500

{
"success": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred",
"details": {
"requestId": "req_abc123xyz"
}
}
}

Resolution: Retry the request. If persistent, contact support with the requestId.


SERVICE_UNAVAILABLE

Status: 503

{
"success": false,
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Service temporarily unavailable",
"details": {
"reason": "maintenance",
"estimatedRecovery": "2024-01-15T12:00:00Z"
}
}
}

Resolution: Wait for scheduled maintenance to complete.


DATABASE_ERROR

Status: 500

{
"success": false,
"error": {
"code": "DATABASE_ERROR",
"message": "Database operation failed"
}
}

Resolution: Retry the request. This is typically a transient error.

Error Handling Best Practices

1. Always Check Response Status

const response = await fetch('/api/v1/backups', options);
const data = await response.json();

if (!data.success) {
console.error(`Error ${data.error.code}: ${data.error.message}`);
// Handle specific error codes
switch (data.error.code) {
case 'TOKEN_EXPIRED':
await refreshToken();
break;
case 'RATE_LIMIT_EXCEEDED':
await sleep(data.error.details.retryAfter * 1000);
break;
default:
showErrorToUser(data.error.message);
}
}

2. Implement Retry Logic

async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
const data = await response.json();

if (data.success) return data;

// Don't retry client errors (4xx)
if (response.status >= 400 && response.status < 500) {
throw new Error(data.error.message);
}

// Retry server errors (5xx)
await sleep(Math.pow(2, i) * 1000); // Exponential backoff
} catch (error) {
if (i === maxRetries - 1) throw error;
}
}
}

3. Handle Rate Limits

async function handleRateLimit(response) {
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 60;
console.log(`Rate limited. Retrying in ${retryAfter}s`);
await sleep(retryAfter * 1000);
return true; // Signal to retry
}
return false;
}

4. Log Errors for Debugging

function logError(error, context) {
console.error({
code: error.code,
message: error.message,
details: error.details,
context,
timestamp: new Date().toISOString()
});
}