Booking Transport

The API plugin owns only protected HTTP admission and byte transport. Extra Chill Events owns booking persistence, venue policy, authorization, idempotency, attachment storage and policy, one-time handoffs, delivery activity, and durable cleanup.

Submit An Inquiry

POST /wp-json/extrachill/v1/venues/{venue}/booking-inquiries

Anonymous, cookie-authenticated, and bearer-authenticated callers use the same Turnstile and atomic fixed-window rate-limit admission. Authentication is optional. When WordPress has validated a caller, Events reads that canonical current user from request context. Multipart and JSON affinity hops use the same signed internal-user transport. Form fields such as user_id, submitter_user_id, and uploader_user_id are rejected and never become authority.

JSON requests provide the inquiry fields directly. Multipart requests provide intake and attachment_purposes as JSON strings plus up to five attachments[] files. Transport limits are 20 MiB per file and 50 MiB in aggregate. Events applies the authoritative filename, MIME, purpose, scan, storage, and venue policy.

An exact retry with the same idempotency_key, fields, and ordered attachment bytes returns the same immutable receipt. Reusing the key with changed input returns booking_idempotency_conflict (409). The receipt contains public_id, venue_term_id, and submitted_at; anonymous admission also returns the capability used for later follow-through. This original admission receipt is the only response allowed to contain a capability.

Artist Follow-Through

All artist follow-through endpoints are POST routes under the venue affinity boundary:

OperationPathAdmission
Safe status/wp-json/extrachill/v1/venues/{venue}/booking-inquiries/follow-through/statusBounded public-read rate limit
Request correction/wp-json/extrachill/v1/venues/{venue}/booking-inquiries/follow-through/correctionTurnstile, then bounded public-write rate limit
Withdraw or request cancellation/wp-json/extrachill/v1/venues/{venue}/booking-inquiries/follow-through/withdrawalTurnstile, then bounded public-write rate limit
Recover admission receipt/wp-json/extrachill/v1/venues/{venue}/booking-inquiries/follow-through/receipt-recoveryTurnstile, then bounded public-write rate limit

Status accepts body fields public_id and optional capability. Authenticated callers may omit the capability, including when opening status in a new tab. Correction and withdrawal also require expected_version and idempotency_key; correction requires correction of at most 2,000 characters. Receipt recovery accepts public_id, contact_email, idempotency_key, and turnstile_response. Capabilities are exactly 64 lowercase hexadecimal characters and are accepted only in POST bodies. They must never be placed in URLs, query strings, headers, logs, errors, or responses. Caller identity and recovery attestation fields are rejected; authenticated authority always comes from the canonical WordPress current user. Turnstile tokens and admission state are consumed by the API and are never forwarded to Events.

The API invokes only Events abilities whose show_in_rest metadata is exactly false and returns strict artist-safe projections. It does not expose venue notes, contact data, staff identities, deal terms, activity, attachments, provider data, or scheduler state. Before ability execution, the API derives required venue_term_id exclusively from the matched route path; body or query venue values cannot override it. Events validates that venue before any mutation or receipt email side effect. Every Events result must also contain the same internal venue_term_id as a defense-in-depth check. Missing or mismatched affinity fails closed, and the internal field is always stripped from public responses. Recovery maps an affinity failure to the same neutral response rather than disclosing the mismatch.

Unknown inquiry IDs, wrong capabilities, wrong venues, and wrong authenticated users all return the same booking_inquiry_unavailable (404) response, except recovery remains neutral. A stale correction or withdrawal returns booking_version_conflict (409) with only current_version. Receipt recovery returns 202 with {"accepted":true} for matched, unmatched, wrong-venue, and no-longer-applicable inquiry/contact pairs. Validation, Turnstile, rate-limit, missing ability, database, queue, and other infrastructure failures remain privacy-safe actionable non-2xx responses. Affinity loopback failures use booking_follow_through_unavailable (503) on every follow-through route and preserve only bounded Retry-After metadata when supplied. Clients must retain the same idempotency_key when retrying recovery.

Download An Attachment

GET /wp-json/extrachill/v1/events/bookings/{booking_id}/attachments/{attachment_id}/download

The caller must be authenticated and currently authorized by Events for the booking’s exact venue. The API issues and consumes an Events-owned one-time handoff, supports one byte range, and streams at most 20 MiB. HEAD and REST _envelope requests cannot consume a handoff.

Success is 200 or 206 with Content-Disposition, Content-Length, Content-Type, Accept-Ranges, and private no-store headers. The Events correlation is never exposed publicly or accepted from clients. For an affinity hop, nonce-bound internal response metadata transfers it to the outer worker and is stripped before the client response. Only that client-facing worker records completed, failed, interrupted, or partial after the actual stream outcome. Route-affinity spools are mode 0600, bounded, and removed after serving, failure, interruption, or shutdown.

Stable Errors

SituationCodeStatusClient action
REST field validationrest_missing_callback_param or rest_invalid_param400Use data.params and data.details as field paths.
Submitted user authoritybooking_identity_not_allowed400Remove all submitted user ID fields.
Invalid canonical identitybooking_authentication_invalid401Refresh or clear authentication, then retry.
Exact duplicate inquirySuccessful immutable receipt201Treat the returned public_id as the existing receipt.
Changed idempotent retrybooking_idempotency_conflict409Generate a new key only for an intentionally new inquiry.
Missing Turnstile tokenturnstile_missing_token403Render and submit a challenge.
Expired or invalid Turnstile tokenturnstile_failed403Refresh the challenge and retry.
Inquiry rate limitpublic_write_rate_limited429Wait for the Retry-After seconds.
Follow-through read rate limitpublic_read_rate_limited429Wait for the Retry-After seconds.
Follow-through write or recovery rate limitpublic_write_rate_limited429Wait for the Retry-After seconds.
Unknown inquiry, wrong capability, or wrong userbooking_inquiry_unavailable404Do not reveal which authority check failed.
Stale artist mutationbooking_version_conflict409Refresh status and retry with the returned current_version.
Accepted receipt recoverySuccessful neutral response202Keep the same idempotency key for retries.
Follow-through ability or infrastructure unavailablebooking_follow_through_unavailable503Keep the same idempotency key and retry later.
Stale booking configurationbooking_inquiry_stale_config409Refresh configuration before resubmitting.
Intake disabled or unavailablebooking_inquiry_unavailable503Keep the draft and retry later.
Attachment count/upload mismatchbooking_attachment_count_invalid, booking_attachment_upload_failed, or booking_attachment_purpose_mismatch400Correct the multipart request.
Attachment too largebooking_attachment_size_invalid or booking_attachment_aggregate_size_invalid413Remove or reduce files.
Attachment policy rejectionbooking_attachment_rejected400 or 413Show the safe Events message next to the attachment.
Attachment storage or scan unavailablebooking_inquiry_unavailable503Keep the draft and retry later; internals remain hidden.
Uncertain inquiry attachment outcomebooking_inquiry_reconciliation_required503Do not change the idempotency key; retry only when reconciliation allows it.
Download unauthenticatedbooking_attachment_download_unavailable401Authenticate.
Download unauthorized, revoked, missing, expired, replayed, or tamperedbooking_attachment_download_unavailable404Do not reveal which condition occurred.
Download rate limitbooking_attachment_download_rate_limited429Wait for the Retry-After seconds.
Invalid or unsatisfiable rangebooking_attachment_range_unsatisfiable416Retry with one valid range; inspect Content-Range.
Unknown inquiry failurebooking_inquiry_unavailable503Keep the draft and retry later.
Unknown download failurebooking_attachment_download_unavailable502 or 503Retry later without exposing transport internals.

Only the explicit code/message/status/field contracts above are forwarded. An unknown domain error is always booking_inquiry_unavailable (503) regardless of its original status. Errors and logs must never contain temporary paths, storage roots, hashes, storage or object references, handoff tokens, delivery correlations, internal identities, or private bytes.