{"openapi":"3.1.0","info":{"title":"BookAfterwork API","version":"1.1.0","description":"Corporate event sourcing API: parse a natural-language brief, match venues and providers with a deterministic score, dispatch requests, collect quotes and confirm bookings.\n\n## Design rules\n- Scores, prices, capacity and availability come from the database, never from an LLM. Only `/ai/parse-request` uses a language model, and only to structure free text.\n- Every nullable field means \"unknown\": the API never fills a gap with a guess.\n- Venue answers are asynchronous (a human validates). Poll `GET /requests/{request_id}` or subscribe to webhooks.\n\n## Authentication\nSend a partner API key as `Authorization: Bearer bak_live_…` (sandbox keys start with `bak_test_`). `x-api-key` is accepted as a fallback. Keys are scoped; a missing scope returns 403 `INSUFFICIENT_SCOPE`.\n\n## Rate limiting and quotas\nPer key: a per-minute rate limit plus a daily and a monthly quota. Responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`. Exceeding a limit returns 429 with code `RATE_LIMITED` (minute) or `QUOTA_EXCEEDED` (day/month). Write endpoints additionally require an `Idempotency-Key` header, which blocks duplicate quote requests and phantom bookings.\n\n## Asynchronous flow\n1. `POST /ai/parse-request` → structured brief. 2. `POST /requests` → request id. 3. `POST /ai/recommend` → ranked venues. 4. `POST /requests/{id}/dispatch` → venues are contacted. 5. Venue answers arrive asynchronously: webhooks `request.accepted`, `quote.created`. 6. `GET /requests/{id}/quotes` then `POST /bookings` and `POST /bookings/{id}/payment`.","contact":{"name":"BookAfterwork API","email":"contact@bookafterwork.com","url":"https://bookafterwork.com/developpeurs"}},"servers":[{"url":"https://bookafterwork.com/api/public/v1","description":"Production"},{"url":"https://project--fff6a636-ce28-40cb-acf6-6bea22361b20-dev.lovable.app/api/public/v1","description":"Preview / sandbox"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"AI","description":"Brief extraction, ranking and quote comparison."},{"name":"Venues","description":"Venue search and availability."},{"name":"Providers","description":"Event providers: catering, DJ, photo, activities."},{"name":"Requests","description":"Event requests and their dispatch."},{"name":"Bookings","description":"Booking confirmation and payment links."},{"name":"Webhooks","description":"Asynchronous status notifications."}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Partner API key: `Authorization: Bearer bak_live_…` (or `bak_test_…` in sandbox)."},"apiKeyHeader":{"type":"apiKey","in":"header","name":"x-api-key","description":"Fallback header for clients that cannot set Authorization."}},"schemas":{"Error":{"type":"object","description":"Stable error envelope returned by every 4xx/5xx response.","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. See x-error-codes for the full list."},"message":{"type":"string","description":"Human-readable explanation, safe to show to an end user."},"required_scope":{"type":["string","null"],"description":"Scope the API key is missing, when code = INSUFFICIENT_SCOPE."}}}}},"Brief":{"type":"object","description":"Normalized corporate event brief. All fields are nullable: the API never invents a value it did not read in the input.","properties":{"event_type":{"type":["string","null"],"description":"Event family, e.g. afterwork, seminaire, team-building, soiree-entreprise."},"city":{"type":["string","null"],"description":"City name as written by the requester (e.g. Paris, Lyon)."},"area":{"type":["string","null"],"description":"District or neighbourhood (e.g. Paris 11eme)."},"country":{"type":["string","null"],"description":"ISO-3166 country name or code when stated."},"date":{"type":["string","null"],"description":"Event start date, ISO 8601 (YYYY-MM-DD).","format":"date"},"end_date":{"type":["string","null"],"description":"Event end date for multi-day events, ISO 8601.","format":"date"},"start_time":{"type":["string","null"],"description":"Start time, 24h HH:MM."},"end_time":{"type":["string","null"],"description":"End time, 24h HH:MM."},"guests":{"type":["integer","null"],"description":"Number of attendees."},"budget_max":{"type":["number","null"],"description":"Maximum TOTAL budget for the event (not per person)."},"budget_currency":{"type":["string","null"],"description":"ISO-4217 currency code, e.g. EUR."},"venue_type":{"type":["string","null"],"description":"Requested venue family, e.g. bar, rooftop, chateau, salle-reunion."},"food":{"type":["boolean","null"],"description":"True when catering / food is required."},"drinks":{"type":["boolean","null"],"description":"True when drinks are required."},"accommodation":{"type":["boolean","null"],"description":"True when overnight rooms are required."},"privatization":{"type":["boolean","null"],"description":"True when full or partial privatisation is required."},"ambiance":{"type":["string","null"],"description":"Requested atmosphere, e.g. festif, chic, nature, insolite."},"equipment":{"type":"array","description":"Required equipment keys (videoprojecteur, sono, wifi, ...).","items":{"type":"string"}},"activities":{"type":"array","description":"Requested activities (escape-game, karaoke, ...).","items":{"type":"string"}},"services":{"type":"array","description":"Detected external provider categories (catering, dj, photo, mixology, yoga, ...).","items":{"type":"string"}},"constraints":{"type":"array","description":"Free-form constraints (accessibility, dietary, ...).","items":{"type":"string"}}}},"ParseResponse":{"type":"object","required":["brief","confidence_score","missing_fields","extraction_engine"],"properties":{"brief":{"$ref":"#/components/schemas/Brief"},"confidence_score":{"type":"number","description":"Extraction confidence between 0 and 1.","minimum":0,"maximum":1},"missing_fields":{"type":"array","description":"Brief fields required before dispatch that are still unknown (ask the user for these).","items":{"type":"string"}},"ambiguous_fields":{"type":"array","description":"Fields that were guessed and should be confirmed with the user.","items":{"type":"string"}},"extraction_engine":{"type":"string","description":"Engine that produced the brief.","enum":["local","ai"]}}},"MatchReason":{"type":"object","description":"Deterministic explanation for a score component.","required":["code"],"properties":{"code":{"type":"string","description":"Reason code, e.g. city_match, capacity_fits, date_declared_open, slow_response."},"weight":{"type":["number","null"],"description":"Points contributed by this reason."}}},"VenueMatch":{"type":"object","required":["venue_id","name","match_score","availability_status"],"properties":{"venue_id":{"type":"string","description":"Venue identifier (UUID). Use it for dispatch and availability calls.","format":"uuid"},"name":{"type":"string","description":"Venue public name."},"city":{"type":["string","null"],"description":"Venue city."},"district":{"type":["string","null"],"description":"Venue district."},"country":{"type":["string","null"],"description":"Venue country."},"capacity":{"type":["integer","null"],"description":"Maximum seated/standing capacity declared by the venue."},"match_score":{"type":"number","description":"Deterministic score from 0 to 100. Never produced by an LLM.","minimum":0,"maximum":100},"estimated_price":{"type":["number","null"],"description":"Indicative TOTAL price for the brief, in budget_currency. Null when the venue published no price."},"price_per_person":{"type":["number","null"],"description":"Indicative price per attendee. Null when unknown."},"availability_status":{"type":"string","description":"Availability verdict for the requested date.","enum":["available","likely_available","to_confirm","unlikely","unavailable"]},"reasons":{"type":"array","description":"Why the venue scored well.","items":{"type":"string"}},"match_reasons":{"type":"array","items":{"$ref":"#/components/schemas/MatchReason"}},"missing_requirements":{"type":"array","description":"Brief requirements this venue does not satisfy.","items":{"type":"string"}},"response_rate":{"type":["number","null"],"description":"Share of requests this venue answered, 0-1."},"acceptance_rate":{"type":["number","null"],"description":"Share of requests this venue accepted, 0-1."},"average_response_time_hours":{"type":["number","null"],"description":"Recent average answer delay, in hours."},"score_breakdown":{"type":"object","description":"Points per criterion (location, capacity, budget, availability, response, ...).","additionalProperties":{"type":"number"}},"photo":{"type":["string","null"],"description":"Absolute URL of the main photo.","format":"uri"}}},"ProviderMatch":{"type":"object","required":["provider_id","business_name","category","match_score"],"properties":{"provider_id":{"type":"string","description":"Provider identifier (UUID).","format":"uuid"},"business_name":{"type":"string","description":"Provider trading name."},"category":{"type":"string","description":"Provider category key (catering, dj, photo, mixology, yoga, sport, ...)."},"city":{"type":["string","null"],"description":"Provider home city."},"country":{"type":["string","null"],"description":"Provider country."},"match_score":{"type":"number","description":"Deterministic score from 0 to 100.","minimum":0,"maximum":100},"base_price_from":{"type":["number","null"],"description":"Entry price published by the provider. Null when not published."},"price_unit":{"type":["string","null"],"description":"Unit for base_price_from, e.g. per_person, per_event, per_hour."},"currency":{"type":["string","null"],"description":"ISO-4217 currency code."},"rating":{"type":["number","null"],"description":"Average approved review rating, 1-5."},"review_count":{"type":"integer","description":"Number of approved reviews."},"reasons":{"type":"array","description":"Why this provider was ranked.","items":{"type":"string"}},"missing_requirements":{"type":"array","items":{"type":"string"}},"slug":{"type":["string","null"],"description":"Public profile slug: /prestataire/{slug}."},"photo":{"type":["string","null"],"description":"Absolute URL of the main photo.","format":"uri"}}},"SearchVenuesResponse":{"type":"object","required":["results","candidate_count"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/VenueMatch"}},"candidate_count":{"type":"integer","description":"Number of venues evaluated before ranking."}}},"RecommendResponse":{"type":"object","required":["run_id","results","candidate_count"],"properties":{"run_id":{"type":"string","description":"Match run identifier, stored for auditability.","format":"uuid"},"weights":{"type":"object","description":"Weight applied to each scoring criterion for this run.","additionalProperties":{"type":"number"}},"candidate_count":{"type":"integer","description":"Number of venues evaluated."},"results":{"type":"array","items":{"$ref":"#/components/schemas/VenueMatch"}}}},"RecommendProvidersResponse":{"type":"object","required":["results","candidate_count"],"properties":{"candidate_count":{"type":"integer","description":"Number of providers evaluated."},"results":{"type":"array","items":{"$ref":"#/components/schemas/ProviderMatch"}}}},"CreateRequestResponse":{"type":"object","required":["request_id","brief"],"properties":{"request_id":{"type":"string","description":"Event request identifier. Use it for dispatch, quotes and webhooks.","format":"uuid"},"brief":{"$ref":"#/components/schemas/Brief"},"confidence_score":{"type":["number","null"],"description":"Extraction confidence, when the request was created from free text."},"missing_fields":{"type":"array","description":"Fields still required before dispatch.","items":{"type":"string"}}}},"Quote":{"type":"object","required":["quote_id","venue_id","status"],"properties":{"quote_id":{"type":"string","description":"Quote identifier.","format":"uuid"},"quote_number":{"type":["string","null"],"description":"Human-readable quote number."},"venue_id":{"type":"string","description":"Venue that issued the quote.","format":"uuid"},"booking_id":{"type":["string","null"],"description":"Related booking identifier.","format":"uuid"},"amount":{"type":["number","null"],"description":"Total amount including tax (TTC)."},"subtotal":{"type":["number","null"],"description":"Amount excluding tax (HT)."},"vat_amount":{"type":["number","null"],"description":"Tax amount."},"deposit_amount":{"type":["number","null"],"description":"Deposit requested to confirm."},"price_per_person":{"type":["number","null"],"description":"Amount per attendee."},"currency":{"type":["string","null"],"description":"ISO-4217 currency code."},"valid_until":{"type":["string","null"],"description":"Quote expiry date, ISO 8601.","format":"date"},"status":{"type":"string","description":"Quote lifecycle status.","enum":["draft","sent","accepted","declined","expired"]},"created_at":{"type":"string","description":"Creation timestamp, ISO 8601.","format":"date-time"}}},"RequestState":{"type":"object","required":["request_id","status","brief"],"properties":{"request_id":{"type":"string","description":"Event request identifier.","format":"uuid"},"status":{"type":"string","description":"Aggregated request status.","enum":["created","pending","quote_sent","accepted","declined","payment_pending","paid","completed"]},"created_at":{"type":"string","description":"Creation timestamp, ISO 8601.","format":"date-time"},"brief":{"$ref":"#/components/schemas/Brief"},"confidence_score":{"type":["number","null"],"description":"Extraction confidence."},"venues_contacted":{"type":"integer","description":"Number of venues the request was dispatched to."},"bookings":{"type":"array","description":"One entry per contacted venue.","items":{"type":"object","properties":{"booking_id":{"type":"string","description":"Booking identifier.","format":"uuid"},"venue_id":{"type":"string","description":"Venue identifier.","format":"uuid"},"venue_name":{"type":["string","null"],"description":"Venue name."},"status":{"type":"string","description":"Venue answer.","enum":["pending","accepted","declined","cancelled","completed"]},"accepted_at":{"type":["string","null"],"description":"Acceptance timestamp, ISO 8601.","format":"date-time"},"event_date":{"type":["string","null"],"description":"Confirmed event date.","format":"date"},"guest_count":{"type":["integer","null"],"description":"Attendees on the booking."}}}},"quotes":{"type":"array","items":{"$ref":"#/components/schemas/Quote"}}}},"DispatchResponse":{"type":"object","required":["dispatched","skipped"],"properties":{"dispatched":{"type":"integer","description":"Number of venues that received the request."},"skipped":{"type":"integer","description":"Number of venues skipped (already contacted, inactive or closed that day)."},"bookings":{"type":"array","description":"Created booking references, one per contacted venue.","items":{"type":"object","properties":{"booking_id":{"type":"string","description":"Booking identifier.","format":"uuid"},"venue_id":{"type":"string","description":"Venue identifier.","format":"uuid"},"status":{"type":"string","description":"Initial status; always pending until the venue answers.","enum":["pending","concierge","error"]}}}}}},"DispatchProvidersResponse":{"type":"object","required":["dispatched","skipped"],"properties":{"dispatched":{"type":"integer","description":"Number of providers contacted."},"skipped":{"type":"integer","description":"Number of providers skipped (already contacted or inactive)."},"inquiry_ids":{"type":"array","description":"Created provider inquiry identifiers.","items":{"type":"string","format":"uuid"}}}},"CompareQuotesResponse":{"type":"object","properties":{"comparison":{"type":["object","null"],"description":"Deterministic comparison. Null when no priced quote has arrived yet.","properties":{"cheapest_quote_id":{"type":["string","null"],"description":"Lowest total amount.","format":"uuid"},"best_value_quote_id":{"type":["string","null"],"description":"Best amount-per-attendee ratio."},"lowest_total":{"type":["number","null"],"description":"Lowest total amount received."},"highest_total":{"type":["number","null"],"description":"Highest total amount received."},"spread":{"type":["number","null"],"description":"Difference between highest and lowest total."},"average_per_person":{"type":["number","null"],"description":"Average amount per attendee."},"quotes":{"type":"array","items":{"$ref":"#/components/schemas/Quote"}}}},"guests":{"type":["integer","null"],"description":"Attendee count used for per-person figures."},"currency":{"type":["string","null"],"description":"ISO-4217 currency code."},"message":{"type":["string","null"],"description":"Explanation when comparison is null."}}},"AvailabilityResponse":{"type":"object","required":["venue_id","days"],"properties":{"venue_id":{"type":"string","description":"Venue identifier.","format":"uuid"},"accepts_all_dates":{"type":"boolean","description":"True when the venue declared it accepts any date."},"closed_weekdays":{"type":"array","description":"Weekdays the venue is closed (0 = Sunday).","items":{"type":"integer","minimum":0,"maximum":6}},"updated_at":{"type":["string","null"],"description":"Last time the venue refreshed its calendar, ISO 8601.","format":"date-time"},"days":{"type":"array","description":"Published day-level availability.","items":{"type":"object","properties":{"date":{"type":"string","description":"Day, ISO 8601.","format":"date"},"status":{"type":"string","description":"Day status.","enum":["available","unavailable"]}}}}}},"AvailabilityWriteResponse":{"type":"object","required":["updated"],"properties":{"updated":{"type":"integer","description":"Number of days written."}}},"BookingResponse":{"type":"object","required":["booking_id","status","payment_status"],"properties":{"booking_id":{"type":"string","description":"Confirmed booking identifier.","format":"uuid"},"quote_id":{"type":"string","description":"Accepted quote identifier.","format":"uuid"},"venue_id":{"type":"string","description":"Venue identifier.","format":"uuid"},"status":{"type":"string","description":"Booking status after confirmation.","enum":["confirmed"]},"amount":{"type":["number","null"],"description":"Total amount including tax."},"deposit_amount":{"type":["number","null"],"description":"Deposit required."},"payment_status":{"type":"string","description":"Payment state.","enum":["payment_pending","deposit_paid","paid"]}}},"PaymentResponse":{"type":"object","required":["booking_id","payment_status","checkout_url"],"properties":{"booking_id":{"type":"string","description":"Booking identifier.","format":"uuid"},"payment_status":{"type":"string","description":"Current payment state.","enum":["payment_pending","deposit_paid","paid"]},"amount_due":{"type":["number","null"],"description":"Amount still due."},"checkout_url":{"type":"string","description":"Hosted page where a human completes payment.","format":"uri"},"expires_in":{"type":"integer","description":"Seconds before the checkout link expires."}}},"WebhookEndpoint":{"type":"object","required":["id","url","events","is_active"],"properties":{"id":{"type":"string","description":"Endpoint identifier.","format":"uuid"},"url":{"type":"string","description":"HTTPS URL that receives the signed POST.","format":"uri"},"events":{"type":"array","description":"Subscribed events, or [\"*\"] for all.","items":{"type":"string"}},"is_active":{"type":"boolean","description":"False when deliveries are paused."},"created_at":{"type":["string","null"],"description":"Creation timestamp, ISO 8601.","format":"date-time"},"secret":{"type":["string","null"],"description":"Signing secret. Returned only once, at creation."}}},"WebhookEndpointList":{"type":"object","required":["endpoints"],"properties":{"endpoints":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"WebhookEvent":{"type":"object","description":"Body POSTed to a subscribed endpoint. Signature header: X-BookAfterwork-Signature: t=<unix>,v1=<hex hmac sha256 of `<t>.<raw body>`>.","required":["id","event","entity_id","created_at","data"],"properties":{"id":{"type":"string","description":"Unique event identifier (evt_...). Use it to deduplicate."},"event":{"type":"string","description":"Event name.","enum":["request.accepted","request.declined","request.dispatched","quote.created","quote.updated","quote.accepted","booking.created","booking.cancelled","payment.succeeded","payment.failed"]},"entity_id":{"type":"string","description":"Identifier of the request, quote or booking concerned."},"created_at":{"type":"string","description":"Emission timestamp, ISO 8601.","format":"date-time"},"data":{"type":"object","description":"Event payload; shape depends on the event name.","additionalProperties":true}}}}},"paths":{"/ai/parse-request":{"post":{"tags":["AI"],"operationId":"parseRequest","summary":"Extract a structured brief from free text","description":"Turns one sentence into a typed brief. Requires the `ai:parse` scope. A local extractor runs first; the language model is only called for fields it could not read.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string","description":"Free-text description of the event, 12 to 1200 characters.","minLength":12,"maxLength":1200},"locale":{"type":"string","description":"Preferred language for labels.","enum":["fr","fr-ca","en","de","es","it","nl","pt"]}}}}}},"responses":{"200":{"description":"Structured brief with confidence and missing fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParseResponse"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/venues/search":{"post":{"tags":["Venues"],"operationId":"searchVenues","summary":"Search venues by criteria","description":"Deterministic venue search. Scope `venues:read`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"city":{"type":"string","description":"City to search in."},"guests":{"type":"integer","description":"Number of attendees.","minimum":1},"date":{"type":"string","description":"Event date, ISO 8601.","format":"date"},"budget_max":{"type":"number","description":"Maximum total budget."},"venue_type":{"type":"string","description":"Venue family filter."},"ambiance":{"type":"string","description":"Atmosphere filter."},"requirements":{"type":"array","description":"Shorthand requirements (dinner, drinks, equipment keys).","items":{"type":"string"}},"limit":{"type":"integer","description":"Maximum results, 1-25.","minimum":1,"maximum":25,"default":10}}}}}},"responses":{"200":{"description":"Scored venue list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchVenuesResponse"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/ai/recommend":{"post":{"tags":["AI"],"operationId":"recommendVenues","summary":"Rank venues for a brief or an existing request","description":"Returns a ranked shortlist with a score breakdown and explicit reasons. Scope `venues:read`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"request_id":{"type":"string","description":"Existing request to rank for.","format":"uuid"},"brief":{"$ref":"#/components/schemas/Brief"},"limit":{"type":"integer","description":"Maximum results, 1-25.","minimum":1,"maximum":25,"default":6}}}}}},"responses":{"200":{"description":"Ranked recommendations with score breakdown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecommendResponse"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/ai/recommend-providers":{"post":{"tags":["Providers"],"operationId":"recommendProviders","summary":"Rank event providers for a brief or an existing request","description":"Ranks caterers, DJs, photographers and activity providers. Scope `venues:read`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"request_id":{"type":"string","description":"Existing request to rank for.","format":"uuid"},"brief":{"$ref":"#/components/schemas/Brief"},"limit":{"type":"integer","description":"Maximum results, 1-25.","minimum":1,"maximum":25,"default":6}}}}}},"responses":{"200":{"description":"Ranked providers with score breakdown.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecommendProvidersResponse"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/requests":{"post":{"tags":["Requests"],"operationId":"createRequest","summary":"Create an event request","description":"Creates a request from free text, a brief, or both. Scope `requests:create`. Requires an `Idempotency-Key`.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (8-200 chars) per logical operation. Replaying the same key with the same body returns the stored response.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Free-text brief; parsed when `brief` is omitted."},"brief":{"$ref":"#/components/schemas/Brief"},"company_name":{"type":"string","description":"Requesting company, required before dispatch."},"contact_email":{"type":"string","description":"Contact email, required before dispatch.","format":"email"},"contact_name":{"type":"string","description":"Contact person."},"contact_phone":{"type":"string","description":"Contact phone."},"locale":{"type":"string","description":"Preferred language.","enum":["fr","fr-ca","en","de","es","it","nl","pt"]}}}}}},"responses":{"201":{"description":"Created request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRequestResponse"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/requests/{request_id}":{"get":{"tags":["Requests"],"operationId":"getRequest","summary":"Read a request, its venues and quotes","description":"Poll this endpoint to follow an asynchronous request, or subscribe to webhooks instead. Scope `requests:read`.","parameters":[{"name":"request_id","in":"path","required":true,"description":"Event request identifier.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Current request state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestState"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/requests/{request_id}/dispatch":{"post":{"tags":["Requests"],"operationId":"dispatchRequest","summary":"Send the request to selected venues","description":"Contacts the selected venues once each. A venue already contacted for this request is skipped. Scope `requests:create`.","parameters":[{"name":"request_id","in":"path","required":true,"description":"Event request identifier.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (8-200 chars) per logical operation. Replaying the same key with the same body returns the stored response.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["venue_ids"],"properties":{"venue_ids":{"type":"array","description":"Venues to contact, 1-10.","minItems":1,"maxItems":10,"items":{"type":"string","format":"uuid"}},"company_name":{"type":"string","description":"Overrides the company name stored on the request."},"contact_email":{"type":"string","description":"Overrides the contact email stored on the request.","format":"email"},"contact_phone":{"type":"string","description":"Contact phone."},"notes":{"type":"string","description":"Message added to the request sent to the venue."}}}}}},"responses":{"200":{"description":"Dispatch result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DispatchResponse"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/requests/{request_id}/dispatch-providers":{"post":{"tags":["Providers"],"operationId":"dispatchProviders","summary":"Send the request to selected providers","parameters":[{"name":"request_id","in":"path","required":true,"description":"Event request identifier.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (8-200 chars) per logical operation. Replaying the same key with the same body returns the stored response.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["provider_ids"],"properties":{"provider_ids":{"type":"array","description":"Providers to contact, 1-10.","minItems":1,"maxItems":10,"items":{"type":"string","format":"uuid"}},"company_name":{"type":"string","description":"Requesting company."},"contact_email":{"type":"string","description":"Contact email.","format":"email"},"contact_phone":{"type":"string","description":"Contact phone."}}}}}},"responses":{"200":{"description":"Provider dispatch result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DispatchProvidersResponse"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/requests/{request_id}/quotes":{"get":{"tags":["Requests"],"operationId":"listQuotes","summary":"List quotes received for a request","description":"Quotes arrive asynchronously after a venue accepts. Scope `quotes:read`.","parameters":[{"name":"request_id","in":"path","required":true,"description":"Event request identifier.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Quotes received so far, possibly empty.","content":{"application/json":{"schema":{"type":"object","required":["quotes"],"properties":{"quotes":{"type":"array","items":{"$ref":"#/components/schemas/Quote"}}}}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/ai/compare-quotes":{"post":{"tags":["AI"],"operationId":"compareQuotes","summary":"Compare received quotes (deterministic figures)","description":"Arithmetic comparison only: cheapest, best value, spread. No LLM involved. Scope `quotes:read`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["request_id"],"properties":{"request_id":{"type":"string","description":"Event request identifier.","format":"uuid"}}}}}},"responses":{"200":{"description":"Comparison, or null when no priced quote arrived yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompareQuotesResponse"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/venues/{venue_id}/availability":{"get":{"tags":["Venues"],"operationId":"getVenueAvailability","summary":"Read published availability","description":"Only what the venue published itself. An unknown date is reported as `to_confirm` during matching, never as available. Scope `availability:read`.","parameters":[{"name":"venue_id","in":"path","required":true,"description":"Venue identifier.","schema":{"type":"string","format":"uuid"}},{"name":"from","in":"query","description":"Range start, ISO 8601.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","description":"Range end, ISO 8601.","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Published availability.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailabilityResponse"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Venues"],"operationId":"publishVenueAvailability","summary":"Publish availability (venue integrations)","description":"For venue-side integrations (PMS, booking software). Scope `availability:write`.","parameters":[{"name":"venue_id","in":"path","required":true,"description":"Venue identifier.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["days"],"properties":{"days":{"type":"array","description":"Days to write, max 366.","maxItems":366,"items":{"type":"object","required":["date","status"],"properties":{"date":{"type":"string","description":"Day, ISO 8601.","format":"date"},"status":{"type":"string","description":"Day status.","enum":["available","unavailable"]}}}}}}}}},"responses":{"200":{"description":"Number of days written.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailabilityWriteResponse"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings":{"post":{"tags":["Bookings"],"operationId":"createBooking","summary":"Confirm a booking from an accepted quote","description":"Accepts the quote and confirms the booking. Scope `bookings:create`. Requires an `Idempotency-Key` to prevent duplicate bookings.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (8-200 chars) per logical operation. Replaying the same key with the same body returns the stored response.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quote_id"],"properties":{"quote_id":{"type":"string","description":"Quote to accept.","format":"uuid"}}}}}},"responses":{"201":{"description":"Confirmed booking.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingResponse"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{booking_id}/payment":{"post":{"tags":["Bookings"],"operationId":"createBookingPayment","summary":"Get the hosted payment link for a booking","description":"Payment is always completed by a human on a hosted page. Scope `payments:create`.","parameters":[{"name":"booking_id","in":"path","required":true,"description":"Booking identifier.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (8-200 chars) per logical operation. Replaying the same key with the same body returns the stored response.","schema":{"type":"string","minLength":8,"maxLength":200}}],"responses":{"200":{"description":"Checkout URL and payment status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentResponse"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks":{"get":{"tags":["Webhooks"],"operationId":"listWebhookEndpoints","summary":"List the webhook endpoints of this API key","description":"Scope `webhooks:read`. Secrets are never returned again after creation.","responses":{"200":{"description":"Registered endpoints.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointList"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Webhooks"],"operationId":"createWebhookEndpoint","summary":"Register a webhook endpoint","description":"Scope `webhooks:write`. The signing secret is returned once, in the creation response. Max 5 endpoints per key.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique key (8-200 chars) per logical operation. Replaying the same key with the same body returns the stored response.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","description":"HTTPS URL that receives the signed POST.","format":"uri"},"events":{"type":"array","description":"Events to subscribe to, or [\"*\"] for all.","minItems":1,"items":{"type":"string"}}}}}}},"responses":{"201":{"description":"Created endpoint, including its signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{webhook_id}":{"delete":{"tags":["Webhooks"],"operationId":"deleteWebhookEndpoint","summary":"Delete a webhook endpoint","description":"Scope `webhooks:write`.","parameters":[{"name":"webhook_id","in":"path","required":true,"description":"Endpoint identifier.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Endpoint deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}}}}}},"400":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error envelope with a stable machine-readable code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{"request.dispatched":{"post":{"summary":"The request was sent to the selected venues","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Return 2xx quickly; retries use 1, 5, 30, 120 and 720 minute delays."}}}},"request.accepted":{"post":{"summary":"A venue accepted the request (human validation)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Acknowledged."}}}},"request.declined":{"post":{"summary":"A venue declined the request","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Acknowledged."}}}},"quote.created":{"post":{"summary":"A venue issued a quote","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Acknowledged."}}}},"quote.accepted":{"post":{"summary":"A quote was accepted","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Acknowledged."}}}},"booking.created":{"post":{"summary":"A booking was confirmed","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Acknowledged."}}}},"payment.succeeded":{"post":{"summary":"A payment was completed","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Acknowledged."}}}}},"x-scopes":["venues:read","availability:read","availability:write","ai:parse","requests:create","requests:read","quotes:read","bookings:create","payments:create","webhooks:read","webhooks:write"],"x-rate-limits":{"headers":["X-RateLimit-Limit","X-RateLimit-Remaining","X-RateLimit-Reset"],"per_minute":"Set per key (default 60 requests).","per_day":"Set per key (default 1000 requests).","per_month":"Set per key (default 20000 requests).","idempotency":"Required on every write endpoint to block duplicate quote requests and phantom bookings."},"x-webhook-signature":{"header":"X-BookAfterwork-Signature","format":"t=<unix_timestamp>,v1=<hex_hmac_sha256>","signed_payload":"`<t>.<raw_request_body>` signed with the endpoint secret.","retries":[1,5,30,120,720],"retry_unit":"minutes"},"x-webhook-events":["request.accepted","request.declined","request.dispatched","quote.created","quote.updated","quote.accepted","booking.created","booking.cancelled","payment.succeeded","payment.failed"],"x-error-codes":["UNAUTHORIZED","INVALID_API_KEY","KEY_REVOKED","KEY_EXPIRED","INSUFFICIENT_SCOPE","FORBIDDEN","RATE_LIMITED","QUOTA_EXCEEDED","INVALID_PAYLOAD","IDEMPOTENCY_KEY_REQUIRED","IDEMPOTENCY_KEY_REUSED","REQUEST_NOT_FOUND","VENUE_NOT_FOUND","VENUE_NOT_AVAILABLE","MESSAGE_TOO_SHORT","MISSING_DATE","MISSING_GUESTS","MISSING_CONTACT","NO_VENUES","NOT_IMPLEMENTED","INTERNAL_ERROR"]}