The meetergo API uses standard HTTP status codes and returns structured error responses. This guide covers how to handle errors effectively.
All errors follow this structure:
{
"statusCode": 400,
"message": "Validation failed",
"error": "Bad Request",
"errors": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
| Field | Type | Description |
|---|
statusCode | number | HTTP status code |
message | string | Human-readable error message |
error | string | Error type name |
errors | array | Detailed validation errors (optional) |
HTTP Status Codes
Success Codes
| Code | Meaning | When Used |
|---|
200 | OK | Successful GET, PATCH, PUT |
201 | Created | Successful POST creating a resource |
204 | No Content | Successful DELETE |
Client Error Codes
| Code | Meaning | Common Causes |
|---|
400 | Bad Request | Invalid input, validation failed |
401 | Unauthorized | Missing or invalid API key |
403 | Forbidden | Insufficient permissions |
404 | Not Found | Resource doesn’t exist |
406 | Not Acceptable | Limit reached (e.g., max webhooks) |
429 | Too Many Requests | Rate limit exceeded |
Server Error Codes
| Code | Meaning | Action |
|---|
500 | Internal Server Error | Retry with backoff, contact support if persistent |
502 | Bad Gateway | Retry with backoff |
503 | Service Unavailable | Retry with backoff |
Common Errors and Solutions
Authentication Errors
{
"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.
{
"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