Error codes
Every error response from the Openavail API has the same envelope:
{
"error": {
"code": "HOLD_EXPIRED",
"message": "Hold has expired"
},
"pending_notifications": []
}
code is a stable string you can switch on in your agent. message is human-readable prose — do not parse it.
How errors are shaped
All 4xx responses are machine-parseable. 5xx responses indicate a server fault — retry with exponential backoff.
The pending_notifications array is always present on error responses so your agent can drain its notification queue even on failure paths.
Availability and proposal errors
Returned by POST /v1/availability/search. The NO_SLOTS_AVAILABLE, WINDOW_TOO_NARROW,
PAST_TIME, CALENDAR_NOT_FOUND, and CALENDAR_REAUTH_REQUIRED shapes also apply to
POST /v1/booking-proposals where relevant.
| Code | Status | When | What to do |
|---|---|---|---|
NO_SLOTS_AVAILABLE | 409 | No free slots in the requested window. The reason_code field tells you why. next_available: { start, end } points to the nearest opening (always searched by default — 72h lookahead). next_available_exceeds_lookahead: true appears instead when slots exist but fall beyond the lookahead window — retry with a larger next_available_lookahead_hours. | Try a different window or use next_available / increase the lookahead. |
WINDOW_TOO_NARROW | 422 | The window is shorter than the requested meeting duration. The response body includes window_duration_minutes and required_duration_minutes. | Widen the window by at least the difference between those two values. |
WORKING_HOURS_NOT_CONFIGURED | n/a | Warning returned when the calendar owner has no working hours set. Openavail will not restrict bookings by time of day or day of week. | For evenings, weekends, or unusual local times, confirm before booking. Encourage the owner to configure working hours at /rules for stricter guardrails. |
LOOKAHEAD_EXCEEDS_MAXIMUM | 400 | next_available_lookahead_hours exceeds 72. | Reduce the value to 72 or below. |
INVALID_MEETING_CLASS | 400 | The meeting_class string is not registered for this customer. | Check the class name matches one visible in the dashboard. Classes are customer-scoped. |
CALENDAR_NOT_FOUND | 404 | No active primary calendar found for this owner_email. The owner may have no connected calendars, the primary may have been disconnected, or no calendar matches the requested calendar_type. | Ask the owner to connect a calendar and set a primary in the Openavail dashboard (Dashboard → Calendars). |
CALENDAR_REAUTH_REQUIRED | 409 | The connected calendar credentials are no longer usable, so Openavail fails closed instead of serving stale availability or writing calendar events with revoked access. | Ask the owner to reconnect calendar access from the dashboard. |
IDEMPOTENCY_IN_FLIGHT | 409 | A request with this Idempotency-Key is currently being processed. | Wait briefly and retry with the same key to receive the cached response once processing completes. |
IDEMPOTENCY_CONFLICT | 422 | The same Idempotency-Key was reused with a different request body. | This is a client bug. Use a new key for the new request. |
NO_SLOTS_AVAILABLE reason codes
When NO_SLOTS_AVAILABLE is returned, the response body includes a reason_code field. Proposal
creation uses the same field when the requested window cannot produce valid owner-review candidates.
reason_code | Meaning | What to do |
|---|---|---|
NO_FREE_SLOTS | Working day, correct time of day, but the calendar is genuinely busy. | Try a different window or a wider time range. |
DAILY_HOURS_LIMIT | The owner has hit their maximum daily meeting hours. | Try the next working day. |
OFF_DAY | The window falls on a non-working day (e.g. Saturday). | Move the request to a working day. Use next_available for the exact next opening. |
WORKING_HOURS | The window is on a working day but outside the owner's configured hours (e.g. 8pm with 9–17 rules). | Adjust the time of day. Use next_available for the nearest valid slot. |
HARD_BLOCK | The window overlaps a recurring blocked period configured by the owner (e.g. a daily lunch break). | Move the request to a different time. Use next_available — it automatically skips hard blocks. |
next_available is searched for all reason codes by default (72h lookahead, max 72h). If the nearest slot falls just beyond the window, next_available_exceeds_lookahead: true is returned instead — the default 72h covers the full Friday-evening → Monday-morning gap in any timezone, so this flag only appears when agents explicitly pass a shorter lookahead.
Availability warnings
Warnings do not make the request fail. They appear beside successful availability or booking responses so agents can adjust their own behavior.
| Code | When | What to do |
|---|---|---|
WORKING_HOURS_NOT_CONFIGURED | The calendar owner has no working hours set. Openavail can still return slots and accept bookings, but it will not restrict requests by day of week or time of day. | Treat returned slots as bookable, but be careful with evenings, weekends, or unusual local times. Encourage the owner to configure working hours if they want those guardrails. |
Hold errors
Returned by POST /v1/bookings/{hold_id}/confirm.
| Code | Status | When | What to do |
|---|---|---|---|
HOLD_NOT_FOUND | 404 | The hold_id does not exist. | The hold may have been created for a different customer or the ID is wrong. |
HOLD_EXPIRED | 410 | The hold TTL elapsed before confirm was called. | Call POST /v1/availability/search again to find fresh candidates, then create a new hold with POST /v1/holds. |
HOLD_ALREADY_PROMOTED | 409 | The hold was already confirmed into a booking. | This is idempotent — if your Idempotency-Key matches the original confirm, you will receive the cached success response instead of this error. |
SLOT_OUTSIDE_HOLD | 422 | The start/end in the confirm body falls outside the hold's reserved window, or differs from a candidate-scoped hold. | For candidate holds, confirm the exact held start/end. For window holds, pick a valid candidate inside the held window. |
INVALID_MEETING_CLASS | 400 | Same as above. | — |
IDEMPOTENCY_IN_FLIGHT | 409 | Same as above. | — |
IDEMPOTENCY_CONFLICT | 422 | Same as above. | — |
The same hold errors apply to POST /v1/bookings (direct booking), except HOLD_* codes are replaced by CALENDAR_NOT_FOUND when the calendar owner email cannot be resolved to an active primary calendar.
Public scheduling errors
Returned by /public/schedules/... requester endpoints and /v1/public-scheduling/... admin endpoints.
| Code | Status | When | What to do |
|---|---|---|---|
not_found | 404 | The public link, public proposal token, contact verification token, public meeting type, verified domain, requester identity, requester credential, audience, or allocation was not found. Public meeting types also return this when the option is disabled, unpublished, or hidden from the requester by visibility/audience rules. | Refresh the current list or ask the owner for a current link. Do not expose internal link or policy details to requesters. |
attendee_limit_exceeded | 400 | The requester submitted more attendees than the public meeting type allows. The limit includes the requester. | Remove extra guests or choose another meeting type. |
booking_limit_exceeded | 409 | An allocation or booking limit has been reached for the public schedule. | Try another meeting type, time window, or requester domain. |
time_unavailable | 409 | A submitted suggested time is no longer valid. | Ask the requester to choose another suggested time or a wider window. |
rate_limited | 429 | Too many public scheduling requests were made in the current rate-limit window. | Back off and retry later. |
invalid_alias | 400 | A public link path is reserved or invalid. | Choose a different readable link path. |
duplicate_alias | 409 | A public link path is already in use. | Choose a different readable link path. |
duplicate_public_meeting_type | 409 | A public meeting type slug already exists on that link. | Choose another meeting type name/path. |
invalid_public_preempt_policy | 400 | The public meeting type policy is more flexible than the mapped internal meeting class. | Choose Inherit class or a stricter public policy. |
invalid_domain | 400 | A verified-domain or pending-domain value is not a valid domain. | Fix the domain and try again. |
duplicate_domain | 409 | The domain challenge already exists. | Use the existing domain row and run DNS check there. |
domain_not_verified | 422 | A requester identity or audience member references a domain whose DNS challenge is not verified. | Run DNS check successfully before using that domain for identities or verified-domain audience members. |
domain_in_use | 409 | A verified domain is still used by public scheduling identities or audience rules. | Remove dependent identities or audience members before deleting the domain. |
duplicate_audience | 409 | An audience with that name already exists. | Choose another audience name. |
not_pending | 409 | A contact verification is already resolved, or a public proposal can no longer be withdrawn. | Show the current status. Booked requests are not cancelled through the public withdraw endpoint. |
Public scheduling errors still use the standard error envelope:
{
"error": {
"code": "time_unavailable",
"message": "That suggested time is no longer available"
},
"pending_notifications": []
}
The public requester UI should map these to short messages. Avoid exposing internal IDs, hidden meeting type state, raw availability, or owner policy details.
Arbitration reject reasons
When arbitration rejects a booking the response code is always ARBITRATION_REJECTED (409). The reason field carries a stable sub-code:
| Reason | What it means | What to do |
|---|---|---|
NO_CAPACITY | The slot is already committed by a booking of equal or higher priority, or falls inside a hard block. | Try a different slot. If CounterPropose alternatives are returned, pick from those. |
WORKING_HOURS | The slot is on a configured working day but falls outside the owner's working hours (e.g. 8pm on a Tuesday with 9–17 hours). | Request a slot within the owner's daily working hours window. Check next_available in the response for the nearest valid slot. |
OFF_DAY | The slot falls on a day that is not a configured working day for this owner (e.g. Saturday or Sunday). | Try a slot on a working day. Call GET /v1/schedule-rules to read the owner's configured working days before suggesting times. |
SACRED_MEETING | An existing booking at that time is sacred and cannot be displaced. A booking is sacred when any of the following is true: it is older than sacred_meeting_age_hours (default 24 h); at least one external attendee has accepted the invite; or the event was created directly in Google/Outlook (not via an agent). | The slot is protected. Choose a different time. |
MAX_DAILY_HOURS | Confirming this booking would exceed the owner's maximum daily meeting hours. | Try the next available day. |
PERMISSION_DENIED_PREEMPT | The slot is held by a lower-priority booking but your agent does not have the preempt permission. | Request the preempt scope when registering the agent, or choose a slot that is not already committed. |
When the decision is CounterPropose rather than Reject, the response also includes an alternatives[] array:
{
"error": { "code": "ARBITRATION_REJECTED", "reason": "COUNTER_PROPOSED", "message": "…" },
"alternatives": [
{ "start": "2026-06-01T15:00:00Z", "end": "2026-06-01T16:00:00Z", "reason_code": "capacity_conflict" }
],
"pending_notifications": []
}
Auth errors
Returned by all endpoints. Auth error codes follow RFC 6750 convention and are lowercase, unlike the business error codes above.
| Code | Status | When | What to do |
|---|---|---|---|
unknown_api_key | 401 | The Bearer token does not match any registered API key. | Check the key was copied correctly. Keys are shown only once at creation. |
api_key_revoked | 401 | The API key has been explicitly revoked. | Mint a new key via the dashboard or POST /v1/agents/{id}/keys. |
agent_disabled | 401 | The agent the key belongs to has been disabled. | Re-enable the agent in the dashboard before issuing requests. |
owner_scope_denied | 403 | The agent's owner_scope does not include the requested calendar owner. | Check the agent's owner_scope setting. An org:* agent can access all owners; a user:<email> agent is restricted to one. |
permission_denied:<perm> | 403 | The agent lacks the required permission for this endpoint (e.g. permission_denied:create_bookings). | Add the permission when registering the agent or update it via PATCH /v1/agents/{id}. |