Error Handling

import { Callout } from 'nextra/components'

Error Handling

Understanding how to handle errors and edge cases when integrating with the Gett B2C API.

HTTP Status Codes

The API uses standard HTTP status codes to indicate the success or failure of requests:

Success Codes

CodeDescription
200OK - Request successful
201Created - Resource created successfully
204No Content - Request successful, no response body (e.g., order cancellation)

Client Error Codes

CodeDescriptionCommon Causes
400Bad RequestInvalid JSON, missing required parameters, malformed data
401UnauthorizedInvalid or expired token, missing Authorization header
402Payment RequiredInsufficient funds, payment method issues
403ForbiddenAccess denied, invalid permissions
404Not FoundInvalid endpoint, order not found, product unavailable
422Unprocessable EntityValid JSON but business logic validation failed
429Too Many RequestsRate limit exceeded

Server Error Codes

CodeDescription
500Internal Server Error
502Bad Gateway
503Service Unavailable
504Gateway Timeout

Error Response Format

All error responses follow a consistent format:

{
  "status": "error",
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error description",
    "details": {
      "field": "Specific field validation error"
    }
  },
  "request_id": "uuid-for-debugging"
}

Common Error Scenarios

Authentication Errors

Invalid Credentials (401)

{
  "status": "error",
  "error": {
    "code": "INVALID_CREDENTIALS",
    "message": "Invalid client credentials provided"
  }
}

Solution: Verify your client_id and client_secret are correct.

Expired Token (401)

{
  "status": "error",
  "error": {
    "code": "TOKEN_EXPIRED",
    "message": "The access token has expired"
  }
}

Solution: Refresh your token using the OAuth endpoint.

Validation Errors

Missing Required Fields (400)

{
  "status": "error",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Required fields are missing",
    "details": {
      "partner_id": "This field is required",
      "user_accepted_terms_and_privacy": "This field is required"
    }
  }
}

Invalid Coordinates (422)

{
  "status": "error",
  "error": {
    "code": "INVALID_LOCATION",
    "message": "Invalid coordinates provided",
    "details": {
      "stops[0].location.lat": "Latitude must be between -90 and 90"
    }
  }
}

Invalid Phone Number (422)

{
  "status": "error",
  "error": {
    "code": "INVALID_PHONE",
    "message": "Phone number format is invalid",
    "details": {
      "stops[0].actions[0].user.phone": "Phone number must be in international format without + sign"
    }
  }
}

Business Logic Errors

Product Not Available (404)

{
  "status": "error",
  "error": {
    "code": "PRODUCT_UNAVAILABLE",
    "message": "The requested product is not available for this route"
  }
}

Terms Not Accepted (422)

{
  "status": "error",
  "error": {
    "code": "TERMS_NOT_ACCEPTED",
    "message": "User must accept terms and privacy policy"
  }
}

Invalid Schedule Time (422)

{
  "status": "error",
  "error": {
    "code": "INVALID_SCHEDULE",
    "message": "Scheduled time must be in the future",
    "details": {
      "scheduled_at": "Cannot schedule orders in the past"
    }
  }
}

Error Handling Best Practices

Implement Retry Logic

For transient errors (500, 502, 503, 504), implement exponential backoff:

async function makeRequestWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      
      if (response.ok) {
        return await response.json();
      }
      
      // Don't retry client errors (4xx)
      if (response.status >= 400 && response.status < 500) {
        throw new Error(`Client error: ${response.status}`);
      }
      
      // Retry server errors (5xx)
      if (attempt === maxRetries) {
        throw new Error(`Server error after ${maxRetries} attempts`);
      }
      
      // Exponential backoff
      await new Promise(resolve => 
        setTimeout(resolve, Math.pow(2, attempt) * 1000)
      );
      
    } catch (error) {
      if (attempt === maxRetries) {
        throw error;
      }
    }
  }
}

Handle Token Expiration

Automatically refresh tokens when they expire:

class GettAPIClient {
  async makeAuthenticatedRequest(url, options) {
    try {
      const response = await fetch(url, {
        ...options,
        headers: {
          ...options.headers,
          'Authorization': `Bearer ${this.token}`
        }
      });

      if (response.status === 401) {
        // Token expired, refresh and retry
        await this.authenticate();
        return await fetch(url, {
          ...options,
          headers: {
            ...options.headers,
            'Authorization': `Bearer ${this.token}`
          }
        });
      }

      return response;
    } catch (error) {
      throw new Error(`Request failed: ${error.message}`);
    }
  }
}

Validate Input Before Sending

Prevent common validation errors by validating input client-side:

function validateOrderData(orderData) {
  const errors = [];

  // Required fields
  if (!orderData.partner_id) {
    errors.push('partner_id is required');
  }
  
  if (!orderData.user_accepted_terms_and_privacy) {
    errors.push('user_accepted_terms_and_privacy must be true');
  }

  // Validate coordinates
  orderData.stops?.forEach((stop, index) => {
    const lat = stop.location?.lat;
    const lng = stop.location?.lng;
    
    if (lat < -90 || lat > 90) {
      errors.push(`stops[${index}].location.lat must be between -90 and 90`);
    }
    
    if (lng < -180 || lng > 180) {
      errors.push(`stops[${index}].location.lng must be between -180 and 180`);
    }
  });

  // Validate phone numbers
  orderData.stops?.forEach((stop, stopIndex) => {
    stop.actions?.forEach((action, actionIndex) => {
      const phone = action.user?.phone;
      if (phone && !/^\d{10,15}$/.test(phone)) {
        errors.push(`stops[${stopIndex}].actions[${actionIndex}].user.phone must be 10-15 digits`);
      }
    });
  });

  return errors;
}

Graceful Error Display

Present errors to users in a helpful way:

function handleApiError(error, response) {
  switch (response.status) {
    case 400:
      return 'Please check your request and try again.';
    case 401:
      return 'Authentication failed. Please try logging in again.';
    case 402:
      return 'Payment is required to complete this request.';
    case 404:
      return 'The requested service is not available for this route.';
    case 422:
      if (error.error?.details) {
        const fieldErrors = Object.values(error.error.details);
        return `Please correct the following: ${fieldErrors.join(', ')}`;
      }
      return 'Please check your information and try again.';
    case 429:
      return 'Too many requests. Please wait a moment and try again.';
    case 500:
    case 502:
    case 503:
    case 504:
      return 'Service temporarily unavailable. Please try again later.';
    default:
      return 'An unexpected error occurred. Please try again.';
  }
}

Debugging Tips

Request IDs

Save the request_id from error responses for support requests:

async function logApiError(error, response) {
  const errorData = await response.json();
  console.error('API Error:', {
    status: response.status,
    code: errorData.error?.code,
    message: errorData.error?.message,
    requestId: errorData.request_id,
    timestamp: new Date().toISOString()
  });
}

Network Issues

Handle network connectivity problems:

async function makeRequest(url, options) {
  try {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout

    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    });

    clearTimeout(timeoutId);
    return response;

  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error('Request timed out');
    } else if (error.name === 'TypeError') {
      throw new Error('Network error - please check your connection');
    }
    throw error;
  }
}
When contacting support, always include the `request_id` and timestamp of the error for faster resolution.