{"openapi":"3.1.0","info":{"title":"Awrora API","version":"2026-09-09","description":"The Awrora booking API. Authenticate with an API key created in Awrora under Inställningar → Appar → För utvecklare (Settings → Apps → For developers): send it as \"Authorization: Bearer aur_…\". The key decides which organization you are reading — there is no organization parameter. All timestamps are ISO 8601 with the offset of the organization's timezone; all amounts are integers in the currency's minor unit. Error messages are English; branch on the \"code\" field, never on the message."},"servers":[{"url":"https://your-site.awrora.app/api"}],"tags":[{"name":"Account","description":"Your key and this document."},{"name":"Experiences","description":"The catalogue."},{"name":"Availability","description":"Departures and live seat counts."},{"name":"Bookings","description":"Bookings on departures."},{"name":"Customers","description":"The customer register."},{"name":"Gift cards","description":"Issued gift cards and their balances."},{"name":"Webhooks","description":"Signed, retried event delivery to your own endpoints. See https://docs.awrora.se/api/webhooks for the envelope, the signature and the retry schedule."},{"name":"Events","description":"Everything that has happened, newest first."}],"paths":{"/v1/me":{"get":{"operationId":"getMe","summary":"Who am I","description":"Returns the organization and the API key behind the request. Use it to verify a key before building anything else. No scope required.","tags":["Account"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The organization and the key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/experiences":{"get":{"operationId":"listExperiences","summary":"List experiences","description":"Every experience in the organization, published and unpublished. Requires the \"experiences:read\" scope.","tags":["Experiences"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"How many results to return, 1-100. Defaults to 25.","schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"name":"order","in":"query","required":false,"description":"Sort by creation time: \"desc\" (newest first, default) or \"asc\".","schema":{"default":"desc","type":"string","enum":["asc","desc"]}},{"name":"starting_after","in":"query","required":false,"description":"The next_cursor from a previous page. Opaque — pass it back verbatim.","schema":{"type":"string","minLength":1}},{"name":"published","in":"query","required":false,"description":"Filter on publication state. Omit to get both published and unpublished.","schema":{"type":"string","enum":["true","false"]}},{"name":"updated_since","in":"query","required":false,"description":"Only experiences changed at or after this ISO 8601 timestamp.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of experiences.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperiencePage"}}}},"400":{"description":"Invalid request — codes \"validation_failed\" or, on an endpoint that requires one, \"idempotency_key_required\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/experiences/{id}":{"get":{"operationId":"getExperience","summary":"Get an experience","description":"One experience by id. Requires the \"experiences:read\" scope.","tags":["Experiences"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the experience.","schema":{"type":"string"}}],"responses":{"200":{"description":"The experience.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experience"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/availability":{"get":{"operationId":"listAvailability","summary":"List departures with live seat counts","description":"Departures in a date window, with seats that already account for confirmed bookings, pending bookings and seats held in someone else's checkout — the same numbers the storefront calendar shows. The window may not exceed 92 days. Requires the \"availability:read\" scope.","tags":["Availability"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"experience_id","in":"query","required":false,"description":"Only departures for this experience (UUID). Omit to get every experience.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},{"name":"from","in":"query","required":true,"description":"First date in the window (YYYY-MM-DD), inclusive.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"to","in":"query","required":false,"description":"Last date in the window (YYYY-MM-DD), inclusive. Defaults to 30 days after \"from\". The window may not exceed 92 days.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}}],"responses":{"200":{"description":"The departures in the window. This endpoint is not paginated: has_more is always false.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeparturePage"}}}},"400":{"description":"Invalid request — codes \"validation_failed\" or, on an endpoint that requires one, \"idempotency_key_required\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was understood but cannot be fulfilled as asked — codes \"validation_failed\" or \"idempotency_key_reused\" (the same Idempotency-Key was already used with a different body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/bookings":{"get":{"operationId":"listBookings","summary":"List bookings","description":"Bookings in the organization, newest first. Requires the \"bookings:read\" scope.","tags":["Bookings"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"How many results to return, 1-100. Defaults to 25.","schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"name":"order","in":"query","required":false,"description":"Sort by creation time: \"desc\" (newest first, default) or \"asc\".","schema":{"default":"desc","type":"string","enum":["asc","desc"]}},{"name":"starting_after","in":"query","required":false,"description":"The next_cursor from a previous page. Opaque — pass it back verbatim.","schema":{"type":"string","minLength":1}},{"name":"status","in":"query","required":false,"description":"Only bookings with this status.","schema":{"type":"string","enum":["pending","confirmed","cancelled"]}},{"name":"experience_id","in":"query","required":false,"description":"Only bookings on this experience (UUID).","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},{"name":"departure_from","in":"query","required":false,"description":"Only bookings whose departure is on or after this date (YYYY-MM-DD). The window to \"departure_to\" may not exceed 92 days; when \"departure_to\" is omitted it defaults to 92 days after this date.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"departure_to","in":"query","required":false,"description":"Only bookings whose departure is on or before this date (YYYY-MM-DD). When \"departure_from\" is omitted it defaults to 92 days before this date. The window may not exceed 92 days.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"updated_since","in":"query","required":false,"description":"Only bookings changed at or after this ISO 8601 timestamp.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of bookings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingPage"}}}},"400":{"description":"Invalid request — codes \"validation_failed\" or, on an endpoint that requires one, \"idempotency_key_required\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createBooking","summary":"Create a booking","description":"Books seats on a departure, exactly as a manual booking made in Awrora does: the same capacity check (including seats held in someone else's checkout), the same customer record, the same timeline entry and the same confirmation email. The booking is recorded with source \"api\". Prices come from the experience's price tiers — the body carries counts, never amounts. Requires the \"bookings:write\" scope and an \"Idempotency-Key\" header.","tags":["Bookings"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"A unique key for this request, 8-200 characters (a UUID is a good choice). Retrying with the same key replays the first response verbatim — same status, same body, plus \"Idempotent-Replayed: true\" — instead of acting twice. Reusing a key with a different body answers 422.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBookingRequest"}}}},"responses":{"200":{"description":"The booking that was created — the same shape GET /v1/bookings/{id} returns. Returned when nothing new was created — the resource already existed, or this is a replay of an earlier request with the same Idempotency-Key.","headers":{"Idempotent-Replayed":{"description":"Present and \"true\" when this response was replayed from a previous request with the same Idempotency-Key. Nothing happened this time.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"201":{"description":"The booking that was created — the same shape GET /v1/bookings/{id} returns.","headers":{"Idempotent-Replayed":{"description":"Present and \"true\" when this response was replayed from a previous request with the same Idempotency-Key. Nothing happened this time.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Invalid request — codes \"validation_failed\" or, on an endpoint that requires one, \"idempotency_key_required\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state — codes \"insufficient_capacity\" (carries \"seats_available\"), \"conflict\", or \"idempotency_in_progress\" when another request with the same Idempotency-Key is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was understood but cannot be fulfilled as asked — codes \"validation_failed\" or \"idempotency_key_reused\" (the same Idempotency-Key was already used with a different body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/bookings/{id}":{"get":{"operationId":"getBooking","summary":"Get a booking","description":"One booking by id. Requires the \"bookings:read\" scope.","tags":["Bookings"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the booking.","schema":{"type":"string"}}],"responses":{"200":{"description":"The booking.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/customers":{"get":{"operationId":"listCustomers","summary":"List customers","description":"The customer register. Internal staff notes and AI briefs are never returned. Requires the \"customers:read\" scope.","tags":["Customers"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"How many results to return, 1-100. Defaults to 25.","schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"name":"order","in":"query","required":false,"description":"Sort by creation time: \"desc\" (newest first, default) or \"asc\".","schema":{"default":"desc","type":"string","enum":["asc","desc"]}},{"name":"starting_after","in":"query","required":false,"description":"The next_cursor from a previous page. Opaque — pass it back verbatim.","schema":{"type":"string","minLength":1}},{"name":"email","in":"query","required":false,"description":"Exact email match. Emails are unique within an organization.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of customers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerPage"}}}},"400":{"description":"Invalid request — codes \"validation_failed\" or, on an endpoint that requires one, \"idempotency_key_required\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createCustomer","summary":"Create or reuse a customer","description":"Adds a customer to the register, keyed on email. An email that already exists answers 200 with the existing customer instead of creating a second one, so this is safe to call on every sync. Requires the \"customers:write\" scope. An \"Idempotency-Key\" header is optional — the endpoint is already idempotent on the email — but is honoured when sent.","tags":["Customers"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional. A unique key, 8-200 characters. Retrying with the same key replays the first response verbatim instead of acting twice.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCustomerRequest"}}}},"responses":{"200":{"description":"The customer. 201 when it was created, 200 when it already existed. Returned when nothing new was created — the resource already existed, or this is a replay of an earlier request with the same Idempotency-Key.","headers":{"Idempotent-Replayed":{"description":"Present and \"true\" when this response was replayed from a previous request with the same Idempotency-Key. Nothing happened this time.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"201":{"description":"The customer. 201 when it was created, 200 when it already existed.","headers":{"Idempotent-Replayed":{"description":"Present and \"true\" when this response was replayed from a previous request with the same Idempotency-Key. Nothing happened this time.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"400":{"description":"Invalid request — codes \"validation_failed\" or, on an endpoint that requires one, \"idempotency_key_required\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was understood but cannot be fulfilled as asked — codes \"validation_failed\" or \"idempotency_key_reused\" (the same Idempotency-Key was already used with a different body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/customers/{id}":{"get":{"operationId":"getCustomer","summary":"Get a customer","description":"One customer by id. Requires the \"customers:read\" scope.","tags":["Customers"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the customer.","schema":{"type":"string"}}],"responses":{"200":{"description":"The customer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateCustomer","summary":"Update a customer","description":"Changes contact details on a customer. Omitted fields are left as they are. Changing the email rewrites the customer's bookings and newsletter record too, the same way the admin customer page does. Requires the \"customers:write\" scope; an \"Idempotency-Key\" header is optional.","tags":["Customers"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the customer.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional. A unique key, 8-200 characters. Retrying with the same key replays the first response verbatim instead of acting twice.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCustomerRequest"}}}},"responses":{"200":{"description":"The updated customer.","headers":{"Idempotent-Replayed":{"description":"Present and \"true\" when this response was replayed from a previous request with the same Idempotency-Key. Nothing happened this time.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"400":{"description":"Invalid request — codes \"validation_failed\" or, on an endpoint that requires one, \"idempotency_key_required\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state — codes \"insufficient_capacity\" (carries \"seats_available\"), \"conflict\", or \"idempotency_in_progress\" when another request with the same Idempotency-Key is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was understood but cannot be fulfilled as asked — codes \"validation_failed\" or \"idempotency_key_reused\" (the same Idempotency-Key was already used with a different body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/bookings/{id}/cancel":{"post":{"operationId":"cancelBooking","summary":"Cancel a booking","description":"Cancels a booking and runs the organization's cancellation flow: refund according to the chosen mode, cancellation email to the guest, notice to staff. Cancelling an already cancelled booking is not an error — it answers 200 with the unchanged booking, so a retry is always safe. Requires the \"bookings:write\" scope and an \"Idempotency-Key\" header.","tags":["Bookings"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the booking.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":true,"description":"A unique key for this request, 8-200 characters (a UUID is a good choice). Retrying with the same key replays the first response verbatim — same status, same body, plus \"Idempotent-Replayed: true\" — instead of acting twice. Reusing a key with a different body answers 422.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelBookingRequest"}}}},"responses":{"200":{"description":"The booking, now cancelled.","headers":{"Idempotent-Replayed":{"description":"Present and \"true\" when this response was replayed from a previous request with the same Idempotency-Key. Nothing happened this time.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Invalid request — codes \"validation_failed\" or, on an endpoint that requires one, \"idempotency_key_required\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was understood but cannot be fulfilled as asked — codes \"validation_failed\" or \"idempotency_key_reused\" (the same Idempotency-Key was already used with a different body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/gift-cards":{"get":{"operationId":"listGiftCards","summary":"List gift cards","description":"Gift cards with their balances. The redeemable code is NEVER returned here — only its last four characters. Requires the \"gift_cards:read\" scope.","tags":["Gift cards"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"How many results to return, 1-100. Defaults to 25.","schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"name":"order","in":"query","required":false,"description":"Sort by creation time: \"desc\" (newest first, default) or \"asc\".","schema":{"default":"desc","type":"string","enum":["asc","desc"]}},{"name":"starting_after","in":"query","required":false,"description":"The next_cursor from a previous page. Opaque — pass it back verbatim.","schema":{"type":"string","minLength":1}},{"name":"status","in":"query","required":false,"description":"Only cards with this status. Status is computed, not stored.","schema":{"type":"string","enum":["active","expired","inactive"]}},{"name":"updated_since","in":"query","required":false,"description":"Only gift cards changed at or after this ISO 8601 timestamp.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of gift cards, without codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftCardPage"}}}},"400":{"description":"Invalid request — codes \"validation_failed\" or, on an endpoint that requires one, \"idempotency_key_required\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/gift-cards/{id}":{"get":{"operationId":"getGiftCard","summary":"Get a gift card","description":"One gift card by id. The redeemable code is included only when you ask for it with \"?include=code\" — otherwise the field is absent. Treat the code as a payment instrument: request it only when you are about to hand it to the recipient. Requires the \"gift_cards:read\" scope.","tags":["Gift cards"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the gift card.","schema":{"type":"string"}},{"name":"include","in":"query","required":false,"description":"Comma-separated extra fields to include. The only value is \"code\", which adds the redeemable code to the response. Omit it unless you actually need the code — it is a payment instrument.","schema":{"type":"string"}}],"responses":{"200":{"description":"The gift card. Carries \"code\" only when \"?include=code\" was sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftCard"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was understood but cannot be fulfilled as asked — codes \"validation_failed\" or \"idempotency_key_reused\" (the same Idempotency-Key was already used with a different body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks":{"get":{"operationId":"listWebhooks","summary":"List webhook endpoints","description":"Every webhook endpoint in the organization, whoever created it. Signing secrets are never returned. Requires the \"webhooks:manage\" scope.","tags":["Webhooks"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The endpoints. This list is not paginated — an organization may have at most 10 — so has_more is always false.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointPage"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createWebhook","summary":"Create a webhook endpoint","description":"Subscribes an https URL to one or more event types. The response carries the signing secret ONCE — store it now, it cannot be read back, only rotated. At most 10 endpoints per organization. Requires the \"webhooks:manage\" scope.","tags":["Webhooks"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional. A unique key, 8-200 characters. Retrying with the same key replays the first response verbatim instead of acting twice.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}}},"responses":{"200":{"description":"The endpoint, with its signing secret. Returned when nothing new was created — the resource already existed, or this is a replay of an earlier request with the same Idempotency-Key.","headers":{"Idempotent-Replayed":{"description":"Present and \"true\" when this response was replayed from a previous request with the same Idempotency-Key. Nothing happened this time.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWithSecret"}}}},"201":{"description":"The endpoint, with its signing secret.","headers":{"Idempotent-Replayed":{"description":"Present and \"true\" when this response was replayed from a previous request with the same Idempotency-Key. Nothing happened this time.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWithSecret"}}}},"400":{"description":"Invalid request — codes \"validation_failed\" or, on an endpoint that requires one, \"idempotency_key_required\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was understood but cannot be fulfilled as asked — codes \"validation_failed\" or \"idempotency_key_reused\" (the same Idempotency-Key was already used with a different body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}":{"get":{"operationId":"getWebhook","summary":"Get a webhook endpoint","description":"One endpoint by id, including its failure counters. The signing secret is never returned. Requires the \"webhooks:manage\" scope.","tags":["Webhooks"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the endpoint.","schema":{"type":"string"}}],"responses":{"200":{"description":"The endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateWebhook","summary":"Update a webhook endpoint","description":"Changes the URL, the subscribed events, the description or the status. Setting status to \"enabled\" on an auto-disabled endpoint reactivates it and resets its failure counter. Requires the \"webhooks:manage\" scope.","tags":["Webhooks"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the endpoint.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional. A unique key, 8-200 characters. Retrying with the same key replays the first response verbatim instead of acting twice.","schema":{"type":"string","minLength":8,"maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookRequest"}}}},"responses":{"200":{"description":"The updated endpoint.","headers":{"Idempotent-Replayed":{"description":"Present and \"true\" when this response was replayed from a previous request with the same Idempotency-Key. Nothing happened this time.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"Invalid request — codes \"validation_failed\" or, on an endpoint that requires one, \"idempotency_key_required\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was understood but cannot be fulfilled as asked — codes \"validation_failed\" or \"idempotency_key_reused\" (the same Idempotency-Key was already used with a different body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteWebhook","summary":"Delete a webhook endpoint","description":"Removes the endpoint and its delivery log. Nothing more is sent to that URL. Requires the \"webhooks:manage\" scope.","tags":["Webhooks"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the endpoint.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted. No content."},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}/deliveries":{"get":{"operationId":"listWebhookDeliveries","summary":"List deliveries for an endpoint","description":"What we sent, what your server answered and when the next attempt is due. This is the answer to \"we never got the booking\". Requires the \"webhooks:manage\" scope.","tags":["Webhooks"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the endpoint.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"How many results to return, 1-100. Defaults to 25.","schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"name":"order","in":"query","required":false,"description":"Sort by creation time: \"desc\" (newest first, default) or \"asc\".","schema":{"default":"desc","type":"string","enum":["asc","desc"]}},{"name":"starting_after","in":"query","required":false,"description":"The next_cursor from a previous page. Opaque — pass it back verbatim.","schema":{"type":"string","minLength":1}},{"name":"status","in":"query","required":false,"description":"Only deliveries in this state.","schema":{"type":"string","enum":["pending","delivered","failed","dead"]}}],"responses":{"200":{"description":"A page of deliveries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryPage"}}}},"400":{"description":"Invalid request — codes \"validation_failed\" or, on an endpoint that requires one, \"idempotency_key_required\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}/deliveries/{deliveryId}/retry":{"post":{"operationId":"retryWebhookDelivery","summary":"Retry a delivery","description":"Queues a failed or dead delivery for another attempt, right away. The attempt counter is kept, so a dead delivery gets one more attempt and dies again if it fails. A delivered or already pending delivery answers 409. Requires the \"webhooks:manage\" scope.","tags":["Webhooks"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the endpoint.","schema":{"type":"string"}},{"name":"deliveryId","in":"path","required":true,"description":"Awrora id (UUID) for the delivery.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional. A unique key, 8-200 characters. Retrying with the same key replays the first response verbatim instead of acting twice.","schema":{"type":"string","minLength":8,"maxLength":200}}],"responses":{"200":{"description":"The delivery, now queued.","headers":{"Idempotent-Replayed":{"description":"Present and \"true\" when this response was replayed from a previous request with the same Idempotency-Key. Nothing happened this time.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state — codes \"insufficient_capacity\" (carries \"seats_available\"), \"conflict\", or \"idempotency_in_progress\" when another request with the same Idempotency-Key is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}/test":{"post":{"operationId":"testWebhook","summary":"Send a test delivery","description":"Queues a \"ping\" event to this endpoint only, regardless of what it subscribes to. It goes through the same signing and the same queue as a real event, so it proves the transport end to end. Requires the \"webhooks:manage\" scope.","tags":["Webhooks"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the endpoint.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional. A unique key, 8-200 characters. Retrying with the same key replays the first response verbatim instead of acting twice.","schema":{"type":"string","minLength":8,"maxLength":200}}],"responses":{"202":{"description":"Queued. The delivery happens after this response — follow it in the deliveries list.","headers":{"Idempotent-Replayed":{"description":"Present and \"true\" when this response was replayed from a previous request with the same Idempotency-Key. Nothing happened this time.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestResult"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}/rotate-secret":{"post":{"operationId":"rotateWebhookSecret","summary":"Rotate the signing secret","description":"Issues a new signing secret and returns it ONCE. The old secret stops working immediately; deliveries signed with the new one will fail until your receiver is updated, then go through on retry. Requires the \"webhooks:manage\" scope.","tags":["Webhooks"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the endpoint.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional. A unique key, 8-200 characters. Retrying with the same key replays the first response verbatim instead of acting twice.","schema":{"type":"string","minLength":8,"maxLength":200}}],"responses":{"200":{"description":"The endpoint, with its new signing secret.","headers":{"Idempotent-Replayed":{"description":"Present and \"true\" when this response was replayed from a previous request with the same Idempotency-Key. Nothing happened this time.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWithSecret"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/events":{"get":{"operationId":"listEvents","summary":"List events","description":"Everything that has happened in the organization, newest first — the same envelopes webhooks deliver. Use it to catch up after downtime, or as a polling trigger instead of webhooks. Scopes are per event type, because an event carries the whole resource: booking.* needs \"bookings:read\", customer.* needs \"customers:read\", gift_card.* needs \"gift_cards:read\". Without ?type you get the types your key may read; asking for a type it may not read answers 403.","tags":["Events"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"How many results to return, 1-100. Defaults to 25.","schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"name":"order","in":"query","required":false,"description":"Sort by creation time: \"desc\" (newest first, default) or \"asc\".","schema":{"default":"desc","type":"string","enum":["asc","desc"]}},{"name":"starting_after","in":"query","required":false,"description":"The next_cursor from a previous page. Opaque — pass it back verbatim.","schema":{"type":"string","minLength":1}},{"name":"type","in":"query","required":false,"description":"Only events of this type. Omit to get every type your key may read.","schema":{"type":"string","enum":["booking.created","booking.confirmed","booking.cancelled","booking.rescheduled","customer.created","customer.updated","gift_card.issued","gift_card.redeemed"]}}],"responses":{"200":{"description":"A page of events.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventPage"}}}},"400":{"description":"Invalid request — codes \"validation_failed\" or, on an endpoint that requires one, \"idempotency_key_required\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/events/{id}":{"get":{"operationId":"getEvent","summary":"Get an event","description":"One event by id — the id from the \"Awrora-Event-Id\" header or from the envelope. Same per-type scope rules as the list.","tags":["Events"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Awrora id (UUID) for the event.","schema":{"type":"string"}}],"responses":{"200":{"description":"The event.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Event"}}}},"401":{"description":"Missing, invalid, revoked or expired API key — codes \"api_key_missing\", \"api_key_invalid\", \"api_key_revoked\", \"api_key_expired\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The API app is off, the plan does not include the API, or the key lacks the required scope — codes \"app_not_enabled\", \"plan_upgrade_required\", \"insufficient_scope\" (the last carries \"required_scope\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource. The same response is returned for a resource that belongs to another organization — code \"not_found\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded — code \"rate_limited\". See the x-ratelimit-* headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Something went wrong on our side — code \"internal_error\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/openapi.json":{"get":{"operationId":"getOpenApiDocument","summary":"This document","description":"The OpenAPI description of this API. No authentication required.","tags":["Account"],"security":[],"responses":{"200":{"description":"The OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An Awrora API key (\"aur_…\"). Create one in Awrora under Inställningar → Appar → För utvecklare; it is shown once."}},"schemas":{"Money":{"type":"object","properties":{"amount_minor":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Amount in the currency's minor unit (öre, cents). 120000 means 1200.00."},"currency":{"type":"string","description":"ISO 4217 currency code, uppercase. For example \"SEK\"."}},"required":["amount_minor","currency"],"additionalProperties":false,"description":"A monetary amount as an integer plus its currency."},"Experience":{"type":"object","properties":{"id":{"type":"string","description":"Awrora id (UUID) for the experience."},"slug":{"type":"string","description":"URL slug, unique within the organization."},"title":{"type":"string","description":"Public title."},"is_published":{"type":"boolean","description":"True when the experience is visible on the storefront. Unpublished experiences are returned too — filter with ?published=."},"excerpt":{"description":"Short teaser text, or null.","type":["string","null"]},"duration_minutes":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Length of the experience in minutes, or null when unset. This is the source: `duration` is derived display text, and a departure without `ends_at` runs this long."},"duration":{"description":"Duration as display text, e.g. \"3 timmar\". Derived from duration_minutes.","type":["string","null"]},"difficulty":{"description":"Difficulty as written by the merchant, or null.","type":["string","null"]},"address":{"description":"Meeting point address, or null.","type":["string","null"]},"latitude":{"description":"WGS84 latitude of the meeting point, or null.","type":["number","null"]},"longitude":{"description":"WGS84 longitude of the meeting point, or null.","type":["number","null"]},"image_url":{"description":"Cover image URL, or null.","type":["string","null"]},"languages":{"type":"array","items":{"type":"string"},"description":"Language codes the experience is offered in. May be empty."},"tags":{"type":"array","items":{"type":"string"},"description":"Merchant-defined tags. May be empty."},"price_from":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Lowest advertised price, or null when the merchant published no machine-readable amount."},"price_tiers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Tier id. Pass this as \"tier_id\" when creating a booking."},"name":{"type":"string","description":"Merchant-facing label, e.g. \"Vuxen\" or \"Barn\"."},"price":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Price per unit, or null when the merchant published no machine-readable amount."},"seats_per_unit":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many seats one unit of this tier consumes. Usually 1; a \"family ticket\" may consume more."}},"required":["id","name","price","seats_per_unit"],"additionalProperties":false,"description":"One price tier on an experience."},"description":"The bookable price tiers. Use the \"id\" of one of these as \"tier_id\" when creating a booking. An experience with no configured tiers reports a single \"standard\" tier built from its advertised price."},"min_participants":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Minimum participants for a departure to run, or null when not set."},"hours_before_booking_closes":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Lead time in hours before departure when booking closes. Null means the organization default applies."},"created_at":{"type":"string","description":"ISO 8601 timestamp with offset."},"updated_at":{"type":"string","description":"ISO 8601 timestamp with offset."}},"required":["id","slug","title","is_published","excerpt","duration_minutes","duration","difficulty","address","latitude","longitude","image_url","languages","tags","price_from","price_tiers","min_participants","hours_before_booking_closes","created_at","updated_at"],"additionalProperties":false,"description":"A bookable experience."},"ExperiencePage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Experience"},"description":"The page of results, in the requested order."},"has_more":{"type":"boolean","description":"True when more results exist after this page. Fetch them with next_cursor."},"next_cursor":{"description":"Pass this back as ?starting_after= to fetch the next page. Null when has_more is false. Opaque — do not parse it.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of results."},"Departure":{"type":"object","properties":{"id":{"type":"string","description":"Awrora id (UUID) for the departure. Use it when creating a booking."},"experience_id":{"type":"string","description":"Id of the experience this departure belongs to."},"starts_at":{"type":"string","description":"Departure start, ISO 8601 with offset in the organization's timezone."},"ends_at":{"description":"Departure end, ISO 8601 with offset, or null when no end time is set.","type":["string","null"]},"seats_total":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Capacity. Null means the departure has no seat limit — it is not zero."},"seats_available":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Seats still bookable, counting confirmed and pending bookings AND seats currently held in someone else's checkout. Null means unlimited, not unknown."},"price":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Lowest advertised price for this experience, or null when none is published."},"booking_closes_at":{"description":"When online booking closes for this departure, ISO 8601 with offset. Null when no lead time is configured.","type":["string","null"]},"status":{"type":"string","enum":["open","sold_out"],"description":"\"open\" when the departure can still be booked, \"sold_out\" when it cannot."}},"required":["id","experience_id","starts_at","ends_at","seats_total","seats_available","price","booking_closes_at","status"],"additionalProperties":false,"description":"One departure of an experience, with live seat counts."},"DeparturePage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Departure"},"description":"The page of results, in the requested order."},"has_more":{"type":"boolean","description":"True when more results exist after this page. Fetch them with next_cursor."},"next_cursor":{"description":"Pass this back as ?starting_after= to fetch the next page. Null when has_more is false. Opaque — do not parse it.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of results."},"Booking":{"type":"object","properties":{"id":{"type":"string","description":"Awrora id (UUID) for the booking."},"booking_number":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Sequential booking number within the organization, or null for legacy rows."},"status":{"type":"string","enum":["pending","confirmed","cancelled"],"description":"pending = created, awaiting payment; confirmed = active; cancelled = cancelled."},"source":{"description":"Where the booking came from: online, admin, agency, ai or api.","type":["string","null"]},"created_at":{"type":"string","description":"ISO 8601 timestamp with offset."},"updated_at":{"type":"string","description":"ISO 8601 timestamp with offset."},"experience":{"type":"object","properties":{"id":{"type":"string","description":"Experience id."},"title":{"type":"string","description":"Experience title."},"slug":{"type":"string","description":"Experience slug."}},"required":["id","title","slug"],"additionalProperties":false,"description":"The experience that was booked."},"departure":{"type":"object","properties":{"id":{"type":"string","description":"Departure id."},"starts_at":{"description":"ISO 8601 with offset, or null when unknown.","type":["string","null"]},"ends_at":{"description":"ISO 8601 with offset, or null.","type":["string","null"]}},"required":["id","starts_at","ends_at"],"additionalProperties":false,"description":"The departure that was booked."},"customer":{"type":"object","properties":{"name":{"type":"string","description":"Guest name on the booking."},"email":{"type":"string","description":"Guest email on the booking."},"phone":{"description":"Guest phone, or null.","type":["string","null"]}},"required":["name","email","phone"],"additionalProperties":false,"description":"The guest details captured at checkout. Not the customer record — see /v1/customers."},"guests":{"type":"array","items":{"type":"object","properties":{"tier_id":{"description":"Id of the price tier, or null for legacy fixed guest types.","type":["string","null"]},"tier_label":{"type":"string","description":"Guest type as shown to the buyer, e.g. \"Vuxen\"."},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Number of guests of this type."},"unit_price":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Price per guest of this type."}},"required":["tier_id","tier_label","count","unit_price"],"additionalProperties":false,"description":"One guest line on a booking."},"description":"Guest lines. May be empty for legacy rows."},"add_ons":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Id of the add-on."},"label":{"type":"string","description":"Add-on name as shown to the buyer."},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Quantity booked."},"unit_price":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Price per unit."}},"required":["id","label","count","unit_price"],"additionalProperties":false,"description":"One add-on line on a booking."},"description":"Add-on lines. Empty when none were bought."},"totals":{"type":"object","properties":{"subtotal":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Sum of guests and add-ons before discounts."},"discount":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Total discount applied. Zero when none."},"gift_card":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Amount paid with gift cards. Zero when none."},"total":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Amount the buyer owes or paid, after discounts and gift cards."}},"required":["subtotal","discount","gift_card","total"],"additionalProperties":false,"description":"Money totals for the booking."},"payment":{"type":"object","properties":{"method":{"description":"Payment method as recorded at checkout, e.g. \"Kort\" or \"Betala på plats\".","type":["string","null"]},"source":{"type":"string","enum":["stripe","on_site","external"],"description":"Where the money came from, not whether it arrived. stripe = Awrora took the payment (card or Stripe invoice); on_site = the guest pays the operator in person; external = the booking was paid in the operator's previous booking system and migrated in, so no refund can be issued through Awrora. Unlike \"method\", which is a human receipt label and is translated, this is a stable machine value."},"status":{"type":"string","enum":["paid","pending","scheduled","cancelled","unpaid","unknown"],"description":"paid = settled; pending = awaiting payment; scheduled = will be invoiced later; cancelled = booking or invoice voided; unpaid = written off; unknown = no payment state recorded."}},"required":["method","source","status"],"additionalProperties":false,"description":"How the booking is paid, and where that payment stands."},"note":{"description":"Free-text note from the buyer or staff, or null.","type":["string","null"]},"manage_url":{"description":"Link where the guest can open and finish their own booking. Only returned to keys with the \"bookings:write\" scope — read-only keys and webhook/event payloads always get null, because anyone holding the link can see the booking and complete it. Also null when no valid link exists (no token, or the token has expired). Treat it as a secret.","type":["string","null"]}},"required":["id","booking_number","status","source","created_at","updated_at","experience","departure","customer","guests","add_ons","totals","payment","note","manage_url"],"additionalProperties":false,"description":"A booking on a departure."},"BookingPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Booking"},"description":"The page of results, in the requested order."},"has_more":{"type":"boolean","description":"True when more results exist after this page. Fetch them with next_cursor."},"next_cursor":{"description":"Pass this back as ?starting_after= to fetch the next page. Null when has_more is false. Opaque — do not parse it.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of results."},"Customer":{"type":"object","properties":{"id":{"type":"string","description":"Awrora id (UUID) for the customer record."},"name":{"type":"string","description":"Customer name."},"email":{"type":"string","description":"Customer email. Unique within the organization."},"phone":{"description":"Phone number, or null.","type":["string","null"]},"tags":{"type":"array","items":{"type":"string"},"description":"Merchant-defined tags. May be empty."},"email_subscription":{"type":"boolean","description":"True when the customer has opted in to marketing email."},"created_at":{"type":"string","description":"ISO 8601 timestamp with offset."}},"required":["id","name","email","phone","tags","email_subscription","created_at"],"additionalProperties":false,"description":"A customer record. Internal staff notes and AI-generated briefs are never exposed — they are the merchant's private working notes."},"CustomerPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Customer"},"description":"The page of results, in the requested order."},"has_more":{"type":"boolean","description":"True when more results exist after this page. Fetch them with next_cursor."},"next_cursor":{"description":"Pass this back as ?starting_after= to fetch the next page. Null when has_more is false. Opaque — do not parse it.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of results."},"GiftCard":{"type":"object","properties":{"id":{"type":"string","description":"Awrora id (UUID) for the gift card."},"code_last4":{"description":"Last four characters of the code, for matching against a receipt without revealing it.","type":["string","null"]},"status":{"type":"string","enum":["active","expired","inactive"],"description":"active = usable; expired = out of validity or fully spent; inactive = deactivated by the merchant."},"initial_amount":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Face value when the card was issued."},"balance":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Remaining balance right now."},"currency":{"type":"string","description":"ISO 4217 currency code for both amounts."},"expires_at":{"description":"ISO 8601 timestamp with offset, or null when the card never expires.","type":["string","null"]},"purchaser":{"type":"object","properties":{"name":{"description":"Name, or null when not recorded.","type":["string","null"]},"email":{"description":"Email, or null when not recorded.","type":["string","null"]}},"required":["name","email"],"additionalProperties":false,"description":"Who bought the card."},"recipient":{"type":"object","properties":{"name":{"description":"Name, or null when not recorded.","type":["string","null"]},"email":{"description":"Email, or null when not recorded.","type":["string","null"]}},"required":["name","email"],"additionalProperties":false,"description":"Who the card was bought for."},"created_at":{"type":"string","description":"ISO 8601 timestamp with offset."},"code":{"description":"The redeemable code. Only present when ?include=code was sent to GET /v1/gift-cards/{id}; absent otherwise. Never in the list and never in events — those carry code_last4. Treat it as a payment instrument.","type":["string","null"]}},"required":["id","code_last4","status","initial_amount","balance","currency","expires_at","purchaser","recipient","created_at"],"additionalProperties":false,"description":"A gift card. Carries the code only when it was explicitly requested."},"GiftCardSummary":{"type":"object","properties":{"id":{"type":"string","description":"Awrora id (UUID) for the gift card."},"code_last4":{"description":"Last four characters of the code, for matching against a receipt without revealing it.","type":["string","null"]},"status":{"type":"string","enum":["active","expired","inactive"],"description":"active = usable; expired = out of validity or fully spent; inactive = deactivated by the merchant."},"initial_amount":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Face value when the card was issued."},"balance":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Remaining balance right now."},"currency":{"type":"string","description":"ISO 4217 currency code for both amounts."},"expires_at":{"description":"ISO 8601 timestamp with offset, or null when the card never expires.","type":["string","null"]},"purchaser":{"type":"object","properties":{"name":{"description":"Name, or null when not recorded.","type":["string","null"]},"email":{"description":"Email, or null when not recorded.","type":["string","null"]}},"required":["name","email"],"additionalProperties":false,"description":"Who bought the card."},"recipient":{"type":"object","properties":{"name":{"description":"Name, or null when not recorded.","type":["string","null"]},"email":{"description":"Email, or null when not recorded.","type":["string","null"]}},"required":["name","email"],"additionalProperties":false,"description":"Who the card was bought for."},"created_at":{"type":"string","description":"ISO 8601 timestamp with offset."}},"required":["id","code_last4","status","initial_amount","balance","currency","expires_at","purchaser","recipient","created_at"],"additionalProperties":false,"description":"A gift card, without its code."},"GiftCardPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/GiftCardSummary"},"description":"The page of results, in the requested order."},"has_more":{"type":"boolean","description":"True when more results exist after this page. Fetch them with next_cursor."},"next_cursor":{"description":"Pass this back as ?starting_after= to fetch the next page. Null when has_more is false. Opaque — do not parse it.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of results."},"Me":{"type":"object","properties":{"organization":{"type":"object","properties":{"id":{"type":"string","description":"Awrora id (UUID) for the organization."},"slug":{"type":"string","description":"Organization slug."},"name":{"type":"string","description":"Organization display name."},"timezone":{"type":"string","description":"IANA timezone. All timestamps use this offset."},"default_currency":{"type":"string","description":"ISO 4217 code amounts are reported in."}},"required":["id","slug","name","timezone","default_currency"],"additionalProperties":false,"description":"The organization this API key belongs to."},"key":{"type":"object","properties":{"id":{"type":"string","description":"Awrora id (UUID) for the API key."},"name":{"description":"The name the merchant gave the key.","type":["string","null"]},"prefix":{"type":"string","description":"First 12 characters of the key, safe to display."},"scopes":{"type":"array","items":{"type":"string"},"description":"Scopes granted to this key."},"created_at":{"type":"string","description":"ISO 8601 timestamp with offset."}},"required":["id","name","prefix","scopes","created_at"],"additionalProperties":false,"description":"The API key used for this request. The secret itself is never returned."}},"required":["organization","key"],"additionalProperties":false,"description":"Who you are: the organization and the key behind this request."},"Event":{"type":"object","properties":{"id":{"type":"string","description":"Awrora id (UUID) for the event. Stable across retries — use it to deduplicate: the same event may be delivered more than once."},"type":{"type":"string","description":"Event type, e.g. \"booking.created\". \"ping\" is only sent by the test endpoint."},"created_at":{"type":"string","description":"When the event happened, ISO 8601 with the organization's offset."},"api_version":{"type":"string","description":"The API version the payload follows. Matches info.version in the OpenAPI document."},"data":{"type":"object","properties":{"object":{"anyOf":[{"$ref":"#/components/schemas/Booking"},{"$ref":"#/components/schemas/Customer"},{"$ref":"#/components/schemas/GiftCardSummary"},{"type":"object","properties":{"message":{"type":"string","description":"A fixed human-readable string."},"endpoint_id":{"type":"string","description":"The endpoint this test was sent to."}},"required":["message","endpoint_id"],"additionalProperties":false,"description":"The payload of a test delivery."}],"description":"The resource the event is about, in the same shape the REST API returns it. Two deliberate differences: gift cards are the list form (GiftCardSummary, code_last4 only — fetch GET /v1/gift-cards/{id}?include=code when you need the code), and bookings carry manage_url as null (fetch GET /v1/bookings/{id} with a bookings:write key when you need it)."}},"required":["object"],"additionalProperties":{},"description":"The payload. \"object\" is always the resource. Some types add fields next to it: \"reason\" on booking.cancelled, \"previous_departure\" on booking.rescheduled, \"redeemed_amount\" and \"booking_id\" on gift_card.redeemed."},"resource_type":{"type":"string","description":"What kind of thing the event is about: \"booking\", \"customer\", \"gift_card\" or \"webhook\"."},"resource_id":{"description":"Awrora id of that resource, or null for events without one.","type":["string","null"]}},"required":["id","type","created_at","api_version","data","resource_type","resource_id"],"additionalProperties":false,"description":"An event, as returned by /v1/events."},"EventPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"},"description":"The page of results, in the requested order."},"has_more":{"type":"boolean","description":"True when more results exist after this page. Fetch them with next_cursor."},"next_cursor":{"description":"Pass this back as ?starting_after= to fetch the next page. Null when has_more is false. Opaque — do not parse it.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of results."},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"string","description":"Awrora id (UUID) for the endpoint."},"url":{"type":"string","description":"The https URL deliveries are POSTed to."},"description":{"description":"Free-text note, or null.","type":["string","null"]},"events":{"type":"array","items":{"type":"string"},"description":"The event types this endpoint receives. Test deliveries ignore this list."},"status":{"type":"string","enum":["enabled","disabled","auto_disabled"],"description":"enabled = receiving; disabled = turned off by the merchant; auto_disabled = turned off by Awrora after 72 hours without a successful delivery. PATCH status back to \"enabled\" to resume — that also resets the failure counter."},"source":{"type":"string","enum":["admin","api"],"description":"\"api\" when created through this API, \"admin\" when created in Awrora."},"consecutive_failures":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Failed deliveries in a row. Reset to zero by the next success."},"last_success_at":{"description":"ISO 8601 with offset, or null.","type":["string","null"]},"last_failure_at":{"description":"ISO 8601 with offset, or null.","type":["string","null"]},"created_at":{"type":"string","description":"ISO 8601 timestamp with offset."},"updated_at":{"type":"string","description":"ISO 8601 timestamp with offset."}},"required":["id","url","description","events","status","source","consecutive_failures","last_success_at","last_failure_at","created_at","updated_at"],"additionalProperties":false,"description":"A webhook endpoint. The signing secret is never returned here."},"WebhookEndpointWithSecret":{"type":"object","properties":{"id":{"type":"string","description":"Awrora id (UUID) for the endpoint."},"url":{"type":"string","description":"The https URL deliveries are POSTed to."},"description":{"description":"Free-text note, or null.","type":["string","null"]},"events":{"type":"array","items":{"type":"string"},"description":"The event types this endpoint receives. Test deliveries ignore this list."},"status":{"type":"string","enum":["enabled","disabled","auto_disabled"],"description":"enabled = receiving; disabled = turned off by the merchant; auto_disabled = turned off by Awrora after 72 hours without a successful delivery. PATCH status back to \"enabled\" to resume — that also resets the failure counter."},"source":{"type":"string","enum":["admin","api"],"description":"\"api\" when created through this API, \"admin\" when created in Awrora."},"consecutive_failures":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Failed deliveries in a row. Reset to zero by the next success."},"last_success_at":{"description":"ISO 8601 with offset, or null.","type":["string","null"]},"last_failure_at":{"description":"ISO 8601 with offset, or null.","type":["string","null"]},"created_at":{"type":"string","description":"ISO 8601 timestamp with offset."},"updated_at":{"type":"string","description":"ISO 8601 timestamp with offset."},"secret":{"type":"string","description":"The signing secret (\"whsec_…\"). Shown ONCE, here. Store it now — it cannot be read back, only rotated."}},"required":["id","url","description","events","status","source","consecutive_failures","last_success_at","last_failure_at","created_at","updated_at","secret"],"additionalProperties":false,"description":"A webhook endpoint together with its signing secret."},"WebhookEndpointPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"},"description":"The page of results, in the requested order."},"has_more":{"type":"boolean","description":"True when more results exist after this page. Fetch them with next_cursor."},"next_cursor":{"description":"Pass this back as ?starting_after= to fetch the next page. Null when has_more is false. Opaque — do not parse it.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of results."},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","description":"Awrora id (UUID) for the delivery attempt record."},"endpoint_id":{"type":"string","description":"The endpoint this delivery goes to."},"event_id":{"type":"string","description":"The event being delivered. Also the id in the envelope."},"event_type":{"type":"string","description":"Event type, copied from the event for filtering."},"status":{"type":"string","enum":["pending","delivered","failed","dead"],"description":"pending = waiting for its next attempt; delivered = the endpoint answered 2xx; failed = given up early (the endpoint was disabled); dead = six attempts failed, we stopped trying. Retry a dead or failed delivery with the retry endpoint."},"attempts":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many attempts have been made."},"next_attempt_at":{"description":"When the next attempt is due, ISO 8601 with offset. Only meaningful while pending.","type":["string","null"]},"last_status_code":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"HTTP status from the last attempt, or null when the request never got a response."},"last_error":{"description":"Short description of the last failure. Never a copy of your response body.","type":["string","null"]},"response_ms":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"How long the last attempt took, in milliseconds."},"created_at":{"type":"string","description":"ISO 8601 timestamp with offset."},"delivered_at":{"description":"ISO 8601 with offset, or null.","type":["string","null"]}},"required":["id","endpoint_id","event_id","event_type","status","attempts","next_attempt_at","last_status_code","last_error","response_ms","created_at","delivered_at"],"additionalProperties":false,"description":"One event on its way to one endpoint."},"WebhookDeliveryPage":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"},"description":"The page of results, in the requested order."},"has_more":{"type":"boolean","description":"True when more results exist after this page. Fetch them with next_cursor."},"next_cursor":{"description":"Pass this back as ?starting_after= to fetch the next page. Null when has_more is false. Opaque — do not parse it.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of results."},"WebhookTestResult":{"type":"object","properties":{"event_id":{"type":"string","description":"The ping event that was written. Also the id in the envelope."},"delivery_id":{"description":"The delivery queued for this endpoint. Follow it in the deliveries list.","type":["string","null"]},"endpoint_status":{"type":"string","enum":["enabled","disabled","auto_disabled"],"description":"The endpoint's status right now. A disabled endpoint gets the delivery row but never the request — the delivery is marked failed with \"endpoint_disabled\"."}},"required":["event_id","delivery_id","endpoint_status"],"additionalProperties":false,"description":"A queued test delivery."},"Error":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable English message. Do not branch on it."},"code":{"type":"string","description":"Stable machine-readable error code. Branch on this, never on \"error\"."}},"required":["error","code"],"additionalProperties":false,"description":"An error response."},"CreateBookingRequest":{"type":"object","properties":{"experience_date_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The departure to book, from GET /v1/availability. A departure that belongs to another organization answers 404, exactly like one that does not exist."},"guests":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"tier_id":{"type":"string","minLength":1,"description":"Price tier id from the experience resource (\"price_tiers[].id\"). Unknown tiers are rejected with \"validation_failed\"."},"count":{"type":"integer","minimum":1,"maximum":500,"description":"How many guests on this tier. At least 1."}},"required":["tier_id","count"],"description":"Guests on one price tier."},"description":"At least one guest line. Several lines may use different price tiers."},"add_ons":{"description":"Optional add-ons. Requires the \"Add-ons\" app to be enabled for the organization.","maxItems":50,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Awrora id (UUID) for the add-on."},"count":{"type":"integer","minimum":1,"maximum":500,"description":"How many of this add-on."}},"required":["id","count"],"description":"An add-on to include on the booking."}},"customer":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"The guest's name, as it should appear on the booking."},"email":{"type":"string","maxLength":200,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"The guest's email. Also the key in the customer register: an existing customer with this email is reused, never duplicated."},"phone":{"description":"The guest's phone. Swedish format (\"+46701234567\" or \"0701234567\") or omitted — an unparseable number is rejected rather than stored as typed.","type":"string","maxLength":200}},"required":["name","email"],"description":"Who the booking is for."},"note":{"description":"Free-text note stored on the booking and shown to staff.","type":"string","maxLength":2000},"payment":{"type":"string","enum":["pay_on_site","invoice","external_paid"],"description":"How the booking is paid. \"pay_on_site\": confirmed, the guest pays the operator on arrival (the platform on-site fee is accrued, exactly as for a checkout booking). \"invoice\": confirmed and flagged for invoicing by staff. \"external_paid\": confirmed and already paid somewhere else — no card is charged and no invoice is created. There is no card option: this API never takes payment."},"send_confirmation_email":{"default":true,"description":"Send the organization's booking confirmation email to the guest. Set false when your own system already told them.","type":"boolean"},"notify_staff":{"default":true,"description":"Send the organization's internal \"new booking\" notification email to its staff, the same one a checkout booking triggers. Honors the organization's own notification settings: if they have switched that notification off, nothing is sent either way. Set false when your integration already tells staff itself.","type":"boolean"}},"required":["experience_date_id","guests","customer","payment"],"description":"A new booking on a departure."},"CancelBookingRequest":{"type":"object","properties":{"refund":{"default":"auto","description":"How to handle money already taken. \"auto\" (default) follows the organization's refund policy, the same way the admin cancel button does. \"none\" keeps the money. Bookings without a card payment are unaffected either way.","type":"string","enum":["auto","none"]},"reason":{"description":"Why it was cancelled. Stored with the cancellation for staff to read.","type":"string","maxLength":200}},"description":"Cancel a booking."},"CreateCustomerRequest":{"type":"object","properties":{"email":{"type":"string","maxLength":200,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"The customer's email. This is the identity: posting an email that already exists returns 200 with the existing customer instead of creating a second one."},"name":{"type":"string","minLength":1,"maxLength":200,"description":"The customer's name."},"phone":{"description":"The customer's phone number.","type":"string","maxLength":200},"email_subscription":{"default":false,"description":"Whether the customer has agreed to marketing email. Defaults to false — consent is something you record, never something we assume.","type":"boolean"}},"required":["email","name"],"description":"A customer in the register."},"UpdateCustomerRequest":{"type":"object","properties":{"name":{"description":"New name.","type":"string","minLength":1,"maxLength":200},"phone":{"description":"New phone number. Pass null to clear it.","anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"email":{"description":"New email. Changing it rewrites the customer's bookings too. An email that already belongs to another customer answers 409 \"conflict\".","type":"string","maxLength":200,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"email_subscription":{"description":"Whether the customer has agreed to marketing email.","type":"boolean"},"tags":{"description":"Replaces the customer's tags. Omit to leave them unchanged.","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":60}}},"description":"Fields to change on a customer. Omitted fields are left as they are."},"CreateWebhookRequest":{"type":"object","properties":{"url":{"type":"string","minLength":1,"maxLength":2048,"description":"The https URL to POST events to. Must be a public address — private and loopback addresses are rejected. Maximum 2048 characters."},"events":{"minItems":1,"maxItems":8,"type":"array","items":{"type":"string","enum":["booking.created","booking.confirmed","booking.cancelled","booking.rescheduled","customer.created","customer.updated","gift_card.issued","gift_card.redeemed"]},"description":"Event types to subscribe to. At least one. \"ping\" cannot be subscribed to — test deliveries are sent regardless of this list."},"description":{"description":"A note to yourself about what this endpoint is for.","type":"string","maxLength":200}},"required":["url","events"],"description":"A new webhook endpoint."},"UpdateWebhookRequest":{"type":"object","properties":{"url":{"type":"string","minLength":1,"maxLength":2048,"description":"The https URL to POST events to. Must be a public address — private and loopback addresses are rejected. Maximum 2048 characters."},"events":{"minItems":1,"maxItems":8,"type":"array","items":{"type":"string","enum":["booking.created","booking.confirmed","booking.cancelled","booking.rescheduled","customer.created","customer.updated","gift_card.issued","gift_card.redeemed"]},"description":"Event types to subscribe to. At least one. \"ping\" cannot be subscribed to — test deliveries are sent regardless of this list."},"description":{"description":"A note about the endpoint. Pass null to clear it.","anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"status":{"description":"Turn the endpoint on or off. Setting \"enabled\" on an auto-disabled endpoint reactivates it and resets its failure counter. You cannot set \"auto_disabled\" yourself — only Awrora does that.","type":"string","enum":["enabled","disabled"]}},"description":"Fields to change on a webhook endpoint. Omitted fields are left as they are."}}}}