Skip to main content

Error Response Format

When an API request fails, Kordless returns a JSON error response with a consistent structure:
{
  "detail": "Human-readable error message",
  "status": 404,
  "type": "not_found"
}

Error Response Fields

detail
string
A human-readable description of the error. Use this for debugging and logging.
status
integer
The HTTP status code (400, 401, 403, 404, 429, 500, etc.)
type
string
A machine-readable error type for programmatic handling.

HTTP Status Codes

Kordless uses standard HTTP status codes to indicate success or failure:

2xx Success

Meaning: Request succeededResponse: Contains the requested data
{
  "bookings": [...]
}
Meaning: Resource successfully createdResponse: Contains the newly created resource
{
  "booking": {
    "id": "book_abc123",
    ...
  }
}
Meaning: Request succeeded with no response bodyUse case: DELETE requests, successful updates with no data to return

4xx Client Errors

Meaning: The request is malformed or contains invalid dataCommon causes:
  • Missing required fields
  • Invalid field values
  • Malformed JSON
{
  "detail": "startsAt is required",
  "status": 400,
  "type": "validation_error"
}
How to fix:
  • Check request body against API documentation
  • Validate all required fields are present
  • Ensure correct data types
Meaning: Authentication failed or credentials are missingCommon causes:
  • Missing x-kordless-key header
  • Invalid or revoked API key
  • Expired credentials
{
  "detail": "Invalid or missing API key",
  "status": 401,
  "type": "authentication_error"
}
How to fix:
  • Verify API key is included in headers
  • Check key hasn’t been revoked
  • Generate a new API key if needed
Meaning: Authentication succeeded but you lack permissionCommon causes:
  • Wrong organization ID
  • Accessing resources from another organization
  • Insufficient permissions
{
  "detail": "Insufficient permissions",
  "status": 403,
  "type": "permission_error"
}
How to fix:
  • Verify x-organization-id header is correct
  • Ensure you’re accessing your own organization’s resources
  • Check API key permissions
Meaning: The requested resource doesn’t existCommon causes:
  • Invalid resource ID
  • Resource was deleted
  • Wrong endpoint URL
{
  "detail": "Booking not found",
  "status": 404,
  "type": "not_found"
}
How to fix:
  • Verify the resource ID is correct
  • Check the endpoint URL
  • Ensure the resource exists in your organization
Meaning: The request conflicts with current stateCommon causes:
  • Time slot already booked
  • Overlapping bookings
  • Resource state conflict
{
  "detail": "Time slot is no longer available",
  "status": 409,
  "type": "conflict_error"
}
How to fix:
  • Query fresh availability
  • Choose a different time slot
  • Resolve the conflict before retrying
Meaning: Request is well-formed but semantically invalidCommon causes:
  • Business logic validation failed
  • Invalid time range
  • Rule violations
{
  "detail": "End time must be after start time",
  "status": 422,
  "type": "validation_error"
}
How to fix:
  • Read the error message carefully
  • Adjust request to meet business rules
  • Validate data before sending
Meaning: Rate limit exceededCommon causes:
  • Too many requests in short time
  • Exceeded per-minute/hour/day limits
{
  "detail": "Rate limit exceeded. Try again in 30 seconds.",
  "status": 429,
  "type": "rate_limit_error"
}
How to fix:
  • Implement exponential backoff
  • Respect rate limit headers
  • Cache responses when possible
  • Contact us for higher limits

5xx Server Errors

Meaning: An unexpected error occurred on our serversWhat to do:
  • Retry the request after a delay
  • Check our status page
  • Contact support if persistent
{
  "detail": "An internal error occurred",
  "status": 500,
  "type": "server_error"
}
Meaning: Service temporarily unavailable (maintenance or overload)What to do:
  • Retry with exponential backoff
  • Check status page for maintenance windows
  • Implement circuit breaker pattern
{
  "detail": "Service temporarily unavailable",
  "status": 503,
  "type": "service_unavailable"
}

Rate Limiting

API requests are rate limited to ensure fair usage and system stability.

Rate Limit Headers

Every response includes rate limit information:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200
X-RateLimit-Limit
integer
Maximum requests allowed in the current window
X-RateLimit-Remaining
integer
Number of requests remaining in current window
X-RateLimit-Reset
integer
Unix timestamp when the rate limit resets

Default Limits

  • 100 requests per minute per API key
  • 1,000 requests per hour per API key
  • 10,000 requests per day per organization

Handling Rate Limits

async function makeRequestWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status === 429) {
      const resetTime = parseInt(response.headers.get('X-RateLimit-Reset'));
      const waitTime = (resetTime * 1000) - Date.now();

      console.log(`Rate limited. Waiting ${waitTime}ms...`);
      await new Promise(resolve => setTimeout(resolve, waitTime));
      continue;
    }

    return response;
  }

  throw new Error('Max retries exceeded');
}

Error Handling Best Practices

Don’t assume success. Always check the HTTP status code:
const response = await fetch(url, options);

if (!response.ok) {
  const error = await response.json();
  throw new Error(`API error: ${error.detail}`);
}
Retry on transient errors (500, 503, 429) with exponential backoff:
async function withRetry(fn, maxAttempts = 3) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      const isRetryable = [429, 500, 503].includes(error.status);
      const isLastAttempt = attempt === maxAttempts - 1;

      if (!isRetryable || isLastAttempt) throw error;

      // Exponential backoff: 1s, 2s, 4s...
      await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
    }
  }
}
Log errors with context for debugging:
try {
  const booking = await createBooking(data);
} catch (error) {
  console.error('Booking creation failed:', {
    error: error.message,
    status: error.status,
    type: error.type,
    requestData: data,
    timestamp: new Date().toISOString()
  });

  // Rethrow or handle appropriately
  throw error;
}
Handle different error types appropriately:
try {
  const booking = await createBooking(data);
} catch (error) {
  switch (error.status) {
    case 401:
      // Refresh credentials
      await refreshApiKey();
      return createBooking(data); // Retry

    case 409:
      // Conflict - time slot taken
      const newSlots = await getAvailability();
      return showUserAlternatives(newSlots);

    case 429:
      // Rate limited - wait and retry
      await sleep(error.retryAfter);
      return createBooking(data);

    default:
      // Show user-friendly error
      showError('Unable to create booking. Please try again.');
      throw error;
  }
}
Prevent duplicate operations when retrying:
const idempotencyKey = `booking-${userId}-${Date.now()}`;

const response = await fetch('/api/calendar/v1/bookings', {
  method: 'POST',
  headers: {
    'x-kordless-key': apiKey,
    'x-organization-id': orgId,
    'Idempotency-Key': idempotencyKey,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(bookingData)
});
Monitor rate limit headers and throttle requests:
class RateLimitedClient {
  constructor() {
    this.remaining = 100;
    this.resetTime = Date.now();
  }

  async request(url, options) {
    // Wait if rate limit exhausted
    if (this.remaining === 0) {
      const waitTime = this.resetTime - Date.now();
      if (waitTime > 0) {
        await new Promise(r => setTimeout(r, waitTime));
      }
    }

    const response = await fetch(url, options);

    // Update rate limit state
    this.remaining = parseInt(
      response.headers.get('X-RateLimit-Remaining')
    );
    this.resetTime = parseInt(
      response.headers.get('X-RateLimit-Reset')
    ) * 1000;

    return response;
  }
}

Common Error Scenarios

Scenario 1: Time Slot No Longer Available

Error:
{
  "detail": "Time slot is no longer available",
  "status": 409,
  "type": "conflict_error"
}
Solution:
try {
  const booking = await createBooking(data);
} catch (error) {
  if (error.status === 409) {
    // Query fresh availability
    const slots = await getAvailability({
      serviceId: data.serviceId,
      from: data.startsAt,
      to: data.endsAt
    });

    // Show user alternative times
    return showAlternativeSlots(slots);
  }
}

Scenario 2: Invalid Organization ID

Error:
{
  "detail": "Organization not found",
  "status": 404,
  "type": "not_found"
}
Solution:
// Verify organization ID format
const ORG_ID = process.env.KORDLESS_ORG_ID;

if (!ORG_ID || !ORG_ID.startsWith('org_')) {
  throw new Error('Invalid organization ID format');
}

// Use in request
const response = await fetch(url, {
  headers: {
    'x-organization-id': ORG_ID
  }
});

Scenario 3: Rate Limit Exceeded

Error:
{
  "detail": "Rate limit exceeded. Try again in 30 seconds.",
  "status": 429,
  "type": "rate_limit_error"
}
Solution:
// Implement exponential backoff
async function withBackoff(fn, maxAttempts = 5) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (error.status !== 429 || attempt === maxAttempts - 1) {
        throw error;
      }

      // Wait with exponential backoff
      const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

Testing Error Handling

Test your error handling with these scenarios:
1

Test Invalid API Key

curl https://api.kordless.ai/api/calendar/v1/bookings \
  -H "x-kordless-key: invalid_key" \
  -H "x-organization-id: your_org_id"
Expected: 401 Unauthorized
2

Test Missing Required Field

curl -X POST https://api.kordless.ai/api/calendar/v1/bookings \
  -H "x-kordless-key: your_key" \
  -H "x-organization-id: your_org_id" \
  -H "Content-Type: application/json" \
  -d '{}'
Expected: 400 Bad Request
3

Test Rate Limiting

Make 101 requests rapidly to trigger rate limit.Expected: 429 Too Many Requests on 101st request

Need Help?

Contact Support

Get help with persistent errors or API issues.

Report Bug

Found an API bug? Report it on GitHub.

Authentication Guide

Review authentication requirements.