Recovery contract
Errors, retries, and idempotency
Every `/v1` response carries request, trace, contract, and server-time metadata. Errors use `application/problem+json` with a stable `code` and recovery guidance.
- Error media type
- application/problem+json
- Correlation
- X-Request-Id + X-Trace-Id + traceparent
- Version
- Aune-Contract-Version
- Backoff
- Retry-After + RateLimit-*
Retry matrix
- `400` or `422`: fix the payload; do not retry unchanged.
- `401`: acquire or refresh a token; do not downgrade to a shared credential.
- `403`: request user approval for the missing scope; do not broaden scopes silently.
- `404`: verify the opaque ID and authenticated principal; do not infer deletion or confirmation.
- `409` with `option_refresh_required: true`: search again and ask the user to choose again.
- `409` idempotency conflict: generate a new key only for a genuinely new user action.
- `429`: wait at least `Retry-After`, then use exponential backoff with jitter.
- `500`, `502`, `503`, `504`: retry reads; retry writes only with the unchanged idempotency key and payload.
Unknown confirmation outcome
If the connection ends after sending confirmation, retry the same option, payload, and idempotency key. A replay returns the same booking. Never search a replacement and confirm it automatically; that could create two user commitments.
Problem shape
Expired option
{
"type": "https://aune-homepage-chat.vercel.app/problems/booking_option_expired",
"title": "booking option expired",
"status": 409,
"detail": "Search for fresh options and ask the user to choose again.",
"instance": "/v1/bookings",
"code": "booking_option_expired",
"option_refresh_required": true,
"api_request_id": "req_...",
"trace_id": "0123456789abcdef0123456789abcdef",
"contract_version": "public_bookings_api_v1"
}