OpheliaDocs
Search docs⌘K
DashboardCreate an account
Reference

Errors

Error shape, codes, rate limits, and how to handle retries safely with idempotency keys.

Errors return a non-2xx status with a JSON body. error is always present. Where a refusal has a stable, machine-readable name it is in error_code; branch on that, not on the wording of error. A few older refusals put their code in error itself (unauthorized, billing_required, free_tier_exhausted, rate_limited, insufficient_scope).

json
// 400 — missing fields (venue search)
{ "error": "missing required fields", "missing": ["vertical", "datetime"] }
 
// 400 — body is not valid JSON, or a field has the wrong type
{ "error": "invalid request body", "field": "party_size", "detail": "party_size must be a whole number, got string" }
 
// 400 — the guest is a placeholder (POST /v1/bookings)
{ "error": "the customer must be the real guest: ...", "error_code": "placeholder_customer" }
 
// 401 — bad or missing key
{ "error": "unauthorized" }
 
// 402 — live key without billing set up
{ "error": "billing_required", "message": "Live API keys require billing..." }
 
// 402 — test key free allowance exhausted
{ "error": "free_tier_exhausted", "limit": 3, "message": "Test API keys are limited to 3 free calls..." }
 
// 403 — the key lacks the scope this endpoint needs
{ "error": "insufficient_scope", "required_scope": "bookings:create" }
 
// 429 — rate limit exceeded (also sent as the Retry-After header)
{ "error": "rate_limited", "message": "rate limit exceeded", "limit": 25, "retry_after_seconds": 180 }
 
// 400 — POST /v1/bookings with a venue_id that has no resolvable provider
{ "error": "provider required: set the 'provider' field or use a venue_id with a known provider mapping" }

The test-key allowance is counted in confirmed test bookings, not calls: searches and unconfirmed attempts never use it up. Once it is used up, search, availability and booking calls on test keys all return 402 free_tier_exhausted.

AgentCard payment

Codes from AgentCard bookings (payment_collection: "agentcard_external"). HTTP codes come back on the call; the rest appear as the failed booking's error_code.

CodeWhereMeaning and what to do
vault_unavailable
409 at create
This venue cannot be paid through AgentCard. Nothing is created. Offer a different venue or time.
agentcard_disabled
503 at create
AgentCard payments are off. Contact support.
(no code)
400 on /continue
Your answer arrived after expires_at. The body has only an error message and the booking is failed (payment_outcome_unknown or payment_approval_expired); read the booking for which.
(no code)
400 on /continue
Malformed psp_response. The booking stays paused; fix the body and retry before expires_at.
payment_declined
error_code
You abandoned the payment with psp_abort. Start a new booking if the end user wants to.
payment_approval_expired
error_code
No answer before expires_at. Start a new booking.
payment_outcome_unknown
error_code
The card may have reached the venue; check before booking again.
payment_setup_failed
error_code
The payment could not be prepared for this venue. Offer a different venue or time.
payment_replay_failed
error_code
The processor may hold the card; check before booking again.
card_declined
error_code
The processor declined the end user's card. Try another card.

Other codes

HTTPCodeMeaning and what to do
400
missing_last_name
This provider (OpenTable) needs a first and last name in customer.name. Nothing was created.
400
slot_not_offered
The datetime is not the time the availability_id was offered at. Book the slot's own datetime, or check availability again.
400
(no code)
validation failed: … names each field, e.g. "datetime: must be in the future". Fix the request; nothing was created.
400
(no code)
could not resolve location "…" on venue search. Send a more specific location, or latitude and longitude.
403
wrong_key_environment
A beta (v3) key was sent to /v1. Use a live or test key.
403
org_suspended
API access for your organization is suspended. Retrying will not help; contact support. reason says why, when given.
404
(no code)
booking not found. The body lists your unfinished bookings from the last day in open_bookings.
404
(no code)
venue not found; run venues/search first. A venue_id is only known after a search returned it.
409
(no code)
Modify on a booking that is not confirmed.
413
(no code)
body too large. Request bodies are limited to 1 MB.
422
modify_unsupported
This provider cannot modify bookings (only OpenTable can). Nothing was attempted.
422
the provider's code
The venue refused the change. The body carries the unchanged booking, still confirmed at its original time.
502
search_failed
Every provider failed for this search; providers_failed lists them. Retry.
502
availability_failed
The provider failed while reading availability; provider names it. Retry.
503
no_search_provider
No provider able to search is configured for this request (400 when you named the providers yourself).
503
billing_check_unavailable, entitlement_check_unavailable
We could not check billing for this key. Retry shortly.

Session codes (live_key_required, invalid_code, reconnect_required and others) are listed on Sessions. A booking whose provider failed is not an HTTP error: it comes back with status: "failed" and its own error_code; see Booking lifecycle.

Rate limits

Limits are per organization, shared by all its API keys, over a fixed 5-minute window. Every metered response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until the window resets). Over the limit you get 429 rate_limited with a Retry-After header.

EndpointPer 5 min
POST /v1/venues/search
25
POST /v1/availability/search
60
POST /v1/bookings
10
POST /v1/bookings/{id}/continue
20
GET /v1/bookings/{id}
300
POST /v1/bookings/{id}/cancel
30
POST /v1/sessions, POST /v1/sessions/{id}/verify
20
GET /v1/sessions/{id}/card, GET /v1/sessions/card
30

Retrying safely

Send an Idempotency-Key header on POST /v1/bookings. Sending the same key again returns the booking it first created and starts nothing new; the key never expires and the body is not compared. Details are on Booking lifecycle. A 5xx or a timeout on create may still have made a booking: retry with the same key rather than a new one.