Two-phase commit

How Openavail prevents race conditions between concurrent agents.

Why two phases

Without a hold, two agents calling POST /v1/availability simultaneously can both see "Tuesday 14:00 is free" and both proceed to book it. One write reaches the calendar first; the second is silently overwritten. Neither agent knows.

Openavail's hold mechanism serialises this: once an agent holds a slot, that slot is invisible to other agents' availability checks until the hold expires or is released. The calendar itself is not modified until confirm.

Phase 1 — Availability check

POST /v1/availability
→ 200 { hold_id, expires_at, slots[] }

Openavail does four things atomically:

  1. Queries the calendar provider for free/busy
  2. Checks existing holds and active bookings
  3. Applies the rule set (working hours, class priorities, buffers)
  4. Creates a hold reservation for the matching slots

The hold reservation is race-safe: no two holds can overlap for the same calendar owner, even under concurrent API calls.

If no slots are available the response is 409 NO_SLOTS_AVAILABLE. Include next_available_lookahead_hours (integer, 1–72, default 24) in the request body to receive a next_available: { start, end } hint — the nearest free slot beyond your window end. If nothing is found within the lookahead period the field is omitted (never null).

Phase 2 — Confirm

POST /v1/bookings/{hold_id}/confirm
→ 200 { booking_id, correlation_id }

The confirm call:

  1. Validates the hold is still active and not expired
  2. Writes the event to Google or Outlook via the calendar adapter
  3. Commits the booking and schedules side-effects (notification emails, webhooks) atomically

Two optional fields on the confirm body control what gets written to the calendar event:

FieldTypeDefault
titlestringMeeting class name
attendees{ email: string; displayName?: string }[][]

The same fields are accepted on POST /v1/bookings (the one-step direct booking endpoint).

Side-effects are guaranteed to execute at least once. If the calendar write fails, Openavail retries automatically.

What happens if you skip confirm

If you call POST /v1/availability and never confirm, the hold expires after ttl_seconds and the slot is released. No calendar write ever occurs. The expired hold is recorded in the audit log with action: hold_expired.

Your agent should always confirm within the TTL. If your processing takes longer than the TTL, call POST /v1/availability again to get a fresh hold.

Idempotency

Both endpoints accept an Idempotency-Key header. If the same key is sent twice within 24 hours, the second call returns the same response as the first without executing again. Use a deterministic key per attempt:

Idempotency-Key: {agent-id}:{job-id}:{attempt-number}