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:
- Queries the calendar provider for free/busy
- Checks existing holds and active bookings
- Applies the rule set (working hours, class priorities, buffers)
- 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:
- Validates the hold is still active and not expired
- Writes the event to Google or Outlook via the calendar adapter
- 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:
| Field | Type | Default |
|---|---|---|
title | string | Meeting 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}