Error Handling

Error Handling

Every error response from the Slotflow API follows a consistent format:

1{
2 "error": {
3 "code": "SLOT_UNAVAILABLE",
4 "message": "The requested slot is no longer available.",
5 "status": 409
6 }
7}
  • code — machine-readable error identifier (use this in your agent’s logic)
  • message — human-readable description (useful for logging, not for parsing)
  • status — HTTP status code (matches the response status)

Error reference

UNAUTHORIZED (401)

When: Missing or invalid API key in the Authorization header.

1{
2 "error": {
3 "code": "UNAUTHORIZED",
4 "message": "Missing or invalid API key.",
5 "status": 401
6 }
7}

Agent strategy: Check that you’re passing the API key correctly. This is never a transient error — don’t retry.

1if (error.code === "UNAUTHORIZED") {
2 throw new Error("Slotflow API key is invalid. Check SLOTFLOW_API_KEY env var.");
3}

NOT_FOUND (404)

When: The resource doesn’t exist, belongs to another organization, or (for humans) has been soft-deleted.

1{
2 "error": {
3 "code": "NOT_FOUND",
4 "message": "Human not found.",
5 "status": 404
6 }
7}

Agent strategy: Verify the ID is correct. If the human was recently deleted (soft delete), they won’t appear in API responses anymore.

SLOT_UNAVAILABLE (409)

When: Another booking was created for the same time slot between your agent’s GET /slots call and your POST /bookings call. This is a race condition — it’s expected in high-volume environments.

1{
2 "error": {
3 "code": "SLOT_UNAVAILABLE",
4 "message": "The requested slot is no longer available.",
5 "status": 409
6 }
7}

Agent strategy: This is the most important error to handle. Retry with the next available slot:

1async function bookWithRetry(humanId, duration, attendee, metadata, maxRetries = 3) {
2 for (let i = 0; i < maxRetries; i++) {
3 // Always re-query slots — previous results are stale
4 const { slots } = await getSlots(humanId, duration);
5 if (slots.length === 0) return null;
6
7 const res = await createBooking({
8 human_id: humanId,
9 starts_at: slots[0].starts_at,
10 duration,
11 ...attendee,
12 metadata,
13 });
14
15 if (res.status === 201) return await res.json();
16 if (res.status !== 409) break; // Only retry on slot conflicts
17 }
18 return null;
19}

INVALID_DURATION (422)

When: The duration parameter doesn’t match any of the human’s configured meeting_durations.

1{
2 "error": {
3 "code": "INVALID_DURATION",
4 "message": "Duration 45 is not allowed. Allowed: 30, 60 minutes.",
5 "status": 422
6 }
7}

Agent strategy: Fetch the human to check their allowed durations, then retry with a valid one:

1if (error.code === "INVALID_DURATION") {
2 const human = await getHuman(humanId);
3 const validDurations = human.availability_rules.meeting_durations;
4 // Use the closest valid duration
5}

OUTSIDE_WORKING_HOURS (422)

When: The booking slot falls outside the human’s working days or hours, and no open override covers it.

1{
2 "error": {
3 "code": "OUTSIDE_WORKING_HOURS",
4 "message": "Slot falls outside working hours.",
5 "status": 422
6 }
7}

Agent strategy: This should rarely happen if your agent uses the GET /slots endpoint first. The slots endpoint only returns valid times. If you see this error, it means the agent is trying to book a time directly without checking slots.

SCHEDULE_BLOCKED (422)

When: The booking slot overlaps with a block override (vacation, recurring meeting, etc.).

1{
2 "error": {
3 "code": "SCHEDULE_BLOCKED",
4 "message": "Slot falls within a schedule block.",
5 "status": 422
6 }
7}

Agent strategy: Same as OUTSIDE_WORKING_HOURS — use GET /slots first, which already filters out blocked times.

LIMIT_REACHED (402)

When: Your organization has used all its monthly booking quota.

1{
2 "error": {
3 "code": "LIMIT_REACHED",
4 "message": "Monthly booking limit reached. Upgrade your plan to continue.",
5 "status": 402
6 }
7}

How limits work by plan:

PlanBehavior when limit hit
FreeHard block. Returns LIMIT_REACHED.
Starter/Growth (auto-purchase OFF)Hard block. Returns LIMIT_REACHED.
Starter/Growth (auto-purchase ON)Auto-buys a $19/500-booking overage pack via Stripe. Booking succeeds.

Agent strategy: On free plans, inform the user their limit is reached. On paid plans, suggest enabling auto-purchase in the dashboard.

VALIDATION_ERROR (422)

When: Missing or malformed fields in the request body.

1{
2 "error": {
3 "code": "VALIDATION_ERROR",
4 "message": "name and timezone are required.",
5 "status": 422
6 }
7}

Agent strategy: Check the message field — it tells you which fields are missing or invalid. This is a developer error, not a runtime error.

Best practices for AI agents

  1. Always use GET /slots before POST /bookings — this prevents most OUTSIDE_WORKING_HOURS and SCHEDULE_BLOCKED errors
  2. Handle 409 with retry logic — slot conflicts are expected, not exceptional
  3. Check error code, not message — messages may change, codes are stable
  4. Log errors with context — include the human_id, requested time, and error code for debugging
  5. Don’t retry on 401, 404, or 422 — these are not transient errors