Skip to main content
The meetergo API uses standard HTTP status codes and returns structured error responses. This guide covers how to handle errors effectively.

Error Response Format

All errors follow this structure:
{
  "statusCode": 400,
  "message": "Validation failed",
  "error": "Bad Request",
  "errors": [
    {
      "field": "email",
      "message": "Invalid email format"
    }
  ]
}
FieldTypeDescription
statusCodenumberHTTP status code
messagestringHuman-readable error message
errorstringError type name
errorsarrayDetailed validation errors (optional)

HTTP Status Codes

Success Codes

CodeMeaningWhen Used
200OKSuccessful GET, PATCH, PUT
201CreatedSuccessful POST creating a resource
204No ContentSuccessful DELETE

Client Error Codes

CodeMeaningCommon Causes
400Bad RequestInvalid input, validation failed
401UnauthorizedMissing or invalid API key
403ForbiddenInsufficient permissions
404Not FoundResource doesn’t exist
406Not AcceptableLimit reached (e.g., max webhooks)
429Too Many RequestsRate limit exceeded

Server Error Codes

CodeMeaningAction
500Internal Server ErrorRetry with backoff, contact support if persistent
502Bad GatewayRetry with backoff
503Service UnavailableRetry with backoff

Common Errors and Solutions

Authentication Errors

Missing Authorization Header

{
  "statusCode": 401,
  "message": "Missing authorization header",
  "error": "Unauthorized"
}
Solution: Include the Authorization header:
-H "Authorization: Bearer YOUR_API_KEY"

Invalid API Key

{
  "statusCode": 401,
  "message": "Invalid API key",
  "error": "Unauthorized"
}
Solution: Verify your API key is correct and complete.

Expired API Key

{
  "statusCode": 401,
  "message": "API key has expired",
  "error": "Unauthorized"
}
Solution: Create a new API key in the dashboard.

Missing User Header

{
  "statusCode": 400,
  "message": "Missing required header 'x-meetergo-api-user-id' for API key authentication",
  "error": "Bad Request"
}
Solution: Include the user context header:
-H "x-meetergo-api-user-id: USER_UUID"

Booking Errors

Time Slot Unavailable

{
  "statusCode": 400,
  "message": "The selected time slot is no longer available",
  "error": "Bad Request"
}
Solution: Query fresh availability and select another slot.
async function bookWithRetry(bookingData, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await createBooking(bookingData);
    } catch (error) {
      if (error.statusCode === 400 &&
          error.message.includes('no longer available')) {
        // Refresh availability
        const windows = await getBookableWindows(bookingData.hostIds[0]);
        if (windows.length === 0) {
          throw new Error('No available slots');
        }
        // Try next available slot
        bookingData.start = windows[0];
      } else {
        throw error;
      }
    }
  }
  throw new Error('Could not book after retries');
}

Invalid Duration

{
  "statusCode": 400,
  "message": "Duration must match one of the allowed durations for this meeting type",
  "error": "Bad Request"
}
Solution: Check the meeting type’s allowed durations before booking.

Missing Host

{
  "statusCode": 400,
  "message": "hostIds or queueId is required",
  "error": "Bad Request"
}
Solution: Provide either hostIds array or queueId for round-robin.

Validation Errors

{
  "statusCode": 400,
  "message": "Validation failed",
  "error": "Bad Request",
  "errors": [
    { "field": "email", "message": "Invalid email format" },
    { "field": "timezone", "message": "Invalid timezone identifier" }
  ]
}
Solution: Check the errors array for specific field issues.
function handleValidationErrors(error) {
  if (error.errors && Array.isArray(error.errors)) {
    const fieldErrors = {};
    for (const err of error.errors) {
      fieldErrors[err.field] = err.message;
    }
    return fieldErrors;
  }
  return { _general: error.message };
}

Error Handling Patterns

JavaScript/TypeScript

interface ApiError {
  statusCode: number;
  message: string;
  error: string;
  errors?: Array<{ field: string; message: string }>;
}

async function callApi<T>(
  url: string,
  options: RequestInit
): Promise<T> {
  const response = await fetch(url, {
    ...options,
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });

  if (!response.ok) {
    const error: ApiError = await response.json();
    throw error;
  }

  return response.json();
}

// Usage with error handling
async function createUser(userData: UserData) {
  try {
    const user = await callApi('/v4/user', {
      method: 'POST',
      body: JSON.stringify(userData),
    });
    return user;
  } catch (error) {
    if (error.statusCode === 401) {
      // Handle authentication error
      console.error('Authentication failed:', error.message);
      throw new Error('Please check your API key');
    }
    if (error.statusCode === 400) {
      // Handle validation error
      console.error('Validation failed:', error.errors);
      throw new Error(`Invalid input: ${error.message}`);
    }
    if (error.statusCode >= 500) {
      // Handle server error
      console.error('Server error:', error.message);
      throw new Error('Service temporarily unavailable');
    }
    throw error;
  }
}

Python

import requests
from typing import Optional, Dict, Any

class MeetergoApiError(Exception):
    def __init__(self, status_code: int, message: str, errors: Optional[list] = None):
        self.status_code = status_code
        self.message = message
        self.errors = errors or []
        super().__init__(message)

def call_api(method: str, endpoint: str, data: Optional[Dict] = None) -> Dict[str, Any]:
    url = f"https://api.meetergo.com{endpoint}"
    headers = {
        'Authorization': f'Bearer {API_KEY}',
        'Content-Type': 'application/json'
    }

    response = requests.request(method, url, headers=headers, json=data)

    if not response.ok:
        error_data = response.json()
        raise MeetergoApiError(
            status_code=error_data.get('statusCode', response.status_code),
            message=error_data.get('message', 'Unknown error'),
            errors=error_data.get('errors')
        )

    return response.json()

# Usage with error handling
def create_booking(booking_data: dict) -> dict:
    try:
        return call_api('POST', '/v4/booking', booking_data)
    except MeetergoApiError as e:
        if e.status_code == 400 and 'no longer available' in e.message:
            # Handle slot taken
            raise ValueError('Time slot is no longer available')
        if e.status_code == 401:
            # Handle auth error
            raise PermissionError('Invalid API key')
        raise

Retry Strategy

For transient errors (429, 5xx), implement exponential backoff:
async function withRetry(fn, maxRetries = 3) {
  let lastError;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;

      // Only retry on transient errors
      if (error.statusCode === 429 ||
          (error.statusCode >= 500 && error.statusCode < 600)) {

        const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
        console.log(`Retry ${attempt + 1}/${maxRetries} after ${delay}ms`);
        await sleep(delay);
      } else {
        // Don't retry client errors (4xx except 429)
        throw error;
      }
    }
  }

  throw lastError;
}

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// Usage
const booking = await withRetry(() => createBooking(data));

Best Practices

Always check status codes - Don’t assume success
Log errors with context - Include request details for debugging
Use exponential backoff - For rate limits and server errors
Parse validation errors - Show specific field errors to users
Handle race conditions - Slots can be booked between availability query and booking
Don’t retry 4xx errors (except 429) - They indicate client issues that won’t resolve with retries