Astrocal
Guides

Errors

Complete reference of Astrocal API error codes, HTTP statuses, and handling guidance.

Every Astrocal API error returns a consistent JSON structure. This page documents all error codes, when they occur, and how to handle them.

Error response format

All errors follow this shape:

{
  "error": {
    "code": "not_found",
    "message": "Booking 'bk_abc123' not found",
    "details": {}
  }
}
FieldTypeDescription
codestringMachine-readable error code (use this for programmatic branching)
messagestringHuman-readable description of what went wrong
detailsobjectOptional field-level errors or additional context (varies by code)

Validation errors

Validation errors occur when the request body or query parameters are malformed or contain invalid values.

validation_error (400 / 422)

Returned when request parameters fail schema validation or contain semantically invalid values.

{
  "error": {
    "code": "validation_error",
    "message": "Invalid request parameters",
    "details": {
      "title": "Required",
      "duration_minutes": "Number must be greater than or equal to 5"
    }
  }
}

Common causes:

  • Missing required fields
  • Values out of range (e.g., duration_minutes below 5 or above 480)
  • Invalid format (e.g., start_time not in ISO 8601, timezone not a valid IANA zone)
  • end_time before start_time in availability rules
  • Date range exceeds the maximum allowed days for availability queries

How to handle: Fix the request based on the details field. Each key in details is the field name, and the value describes what's wrong.

bad_request (400)

Returned for business logic validation that goes beyond schema checks.

{
  "error": {
    "code": "bad_request",
    "message": "Invalid duration: 45. Allowed values: 15, 30, 60"
  }
}

Common causes:

  • Invalid duration value for a variable-duration event type
  • Too many attendees for a group booking
  • Duplicate attendee emails in a multi-attendee booking
  • Waitlist not enabled for the event type
  • Paid event type but no Stripe account connected
  • Expired or revoked invitation
  • Invalid CalDAV preset or missing server_url

How to handle: Read the message field for specific guidance. These are not retryable -- fix the request.

Authentication errors

unauthorized (401)

Returned when authentication is missing, invalid, or expired.

{
  "error": {
    "code": "unauthorized",
    "message": "Invalid API key"
  }
}

Common causes:

  • Missing Authorization: Bearer header
  • API key not found in the database
  • API key has been revoked
  • API key has expired
  • Invalid or missing cancel token on cancel/reschedule endpoints

How to handle: Check that you're sending a valid API key. For cancel/reschedule endpoints, ensure the ?token= parameter matches the cancel_token from the booking response. Do not retry -- fix the credentials.

Authorization errors

forbidden (403)

Returned when the authenticated user lacks permission for the requested action.

{
  "error": {
    "code": "forbidden",
    "message": "Insufficient permissions"
  }
}

How to handle: The API key is valid but doesn't have the required permission. Check that the key belongs to the correct organization and that the user's role allows the action.

org_suspended (403)

Returned when the organization has been suspended.

{
  "error": {
    "code": "org_suspended",
    "message": "Organization is suspended"
  }
}

How to handle: Contact support to resolve the suspension.

plan_limit_exceeded (403)

Returned when the organization has reached its plan's booking limit.

{
  "error": {
    "code": "plan_limit_exceeded",
    "message": "Monthly booking limit reached. Upgrade your plan."
  }
}

How to handle: Upgrade the organization's plan or wait for the limit to reset.

Not found errors

not_found (404)

Returned when the requested resource doesn't exist or isn't accessible to the current API key.

{
  "error": {
    "code": "not_found",
    "message": "Event type 'evt_abc123' not found"
  }
}

Common resources: event types, bookings, calendar connections, webhook endpoints, webhook deliveries, API keys, team members, invitations, waitlist entries.

How to handle: Verify the resource ID is correct. The resource may have been deleted, or it may belong to a different organization.

Conflict errors

Conflict errors indicate the request can't be completed because it contradicts the current state of the system.

conflict (409)

The most common conflict scenarios:

Slot no longer available:

{
  "error": {
    "code": "conflict",
    "message": "The selected time slot is no longer available"
  }
}

This occurs when two requests try to book the same slot simultaneously. Handle by refreshing available slots and asking the user to pick again.

Duplicate slug:

{
  "error": {
    "code": "conflict",
    "message": "Event type with slug 'quick-call' already exists"
  }
}

Cannot reschedule cancelled booking:

{
  "error": {
    "code": "conflict",
    "message": "Cannot reschedule a cancelled booking"
  }
}

Other conflict causes:

  • Slot is full (group booking at max attendees)
  • Calendar account already connected
  • Duplicate team invitation
  • Invitation already accepted
  • Member already a host for the event type
  • Webhook delivery not in failed status (for retries)

How to handle: Read the message to understand the specific conflict. For slot conflicts, refresh availability and retry with a new slot. For duplicate resources, use a different identifier.

booking_cap_reached (409)

Returned when the event type's booking cap has been reached for the current period.

{
  "error": {
    "code": "booking_cap_reached",
    "message": "This event type has reached its booking limit for the current period"
  }
}

How to handle: Check availability first -- the capped: true flag indicates the cap has been reached. If the event type has waitlists enabled, offer the user to join the waitlist instead.

Rate limit errors

rate_limit_exceeded (429)

Returned when you've exceeded the request rate for your plan tier.

Authenticated endpoints (API key):

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Your limit is 60 requests/minute (starter tier).",
    "details": {}
  },
  "limit": 60,
  "window": "1m",
  "reset_at": "2026-03-15T14:01:00Z",
  "retry_after": 45,
  "tier": "starter"
}

Public endpoints (per-IP):

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Please try again later."
  },
  "retry_after": 30
}

Rate limit responses include these headers:

HeaderDescription
Retry-AfterSeconds to wait before retrying
X-RateLimit-LimitMaximum requests allowed in the window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets

Plan limits:

PlanRequests/minuteRequests/day
Free30200
Starter601,000
Pro50010,000
Business2,00050,000

How to handle: Back off and retry after the Retry-After duration. Implement exponential backoff for sustained rate limiting. See the Rate Limits guide for strategies.

Payment errors

bad_gateway (502)

Returned when an upstream payment provider (Stripe) fails during booking creation.

{
  "error": {
    "code": "bad_gateway",
    "message": "Failed to create payment — please try again"
  }
}

How to handle: This is a transient error. Retry the request after a short delay. If it persists, check Stripe's status page.

Server errors

internal_error (500)

Returned for unexpected server-side failures.

{
  "error": {
    "code": "internal_error",
    "message": "An unexpected error occurred"
  }
}

How to handle: This indicates a bug or infrastructure issue on Astrocal's side. Retry once after a short delay. If it persists, contact support.

Best practices

  1. Branch on error.code, not error.message. Messages may change; codes are stable.
  2. Check error.details for field-level errors. Validation errors include per-field descriptions to help you fix the request.
  3. Always handle 409 on bookings. Slot conflicts are expected in concurrent systems. Refresh availability and let the user pick again.
  4. Respect Retry-After headers. Rate limit responses tell you exactly when to retry.
  5. Don't retry 400/401/403/404 errors. These require fixing the request or credentials, not retrying.
  6. Retry 429/500/502 with backoff. These are transient and may resolve on their own.

Next steps

On this page