{"openapi":"3.1.0","info":{"title":"LES Booking API","version":"1.0.0-m2","summary":"Generisk bookingsystem: ressource + krav, én motor, tre brancher.","description":"**API'et er produktet** (SPEC 1.2 #2). Det er ikke et lag oven på admin-UI'et — det er samme lag.\n\n### Læs dette først\n- **Additive ændringer sker uden varsel.** Klienten SKAL ignorere ukendte felter og behandle\n  ukendte enum-værdier som \"ukendt\", ikke som fejl (SPEC 4.1 #5).\n- **Tid** er RFC 3339 **med offset** i lokationens tidszone: `2026-09-07T09:00:00+02:00`.\n  Aldrig naive datoer, aldrig gæstens tidszone (SPEC 4.2, 6.10).\n- **Penge** er heltal i øre plus `currency`. Aldrig float (SPEC 6.1 aksiom 6).\n- **`request_id`** returneres som header `X-Request-Id` på ALLE svar, også succes.\n  Supportsamtaler starter der (SPEC 4.2).\n- **`occupies`** er det spærrede interval inkl. buffer og omstillingstid. Uden det felt\n  bygger enhver integrator sin egen — forkerte — bufferberegning (SPEC 4.6).\n- **To udvidelsespunkter:** `metadata` er opak og valideres aldrig; `custom` er kundens\n  definerede felter, valideret og synlige i UI'et (SPEC 4.1 #8).\n- **Rate limit** pr. nøgle: 120 req/min (burst 40). `/availability` har sit eget loft på\n  600/min. Alle svar bærer `RateLimit-Limit`, `RateLimit-Remaining` og `RateLimit-Reset`;\n  et 429 bærer også `Retry-After` (SPEC 4.2). Et 429 bruger ikke `Idempotency-Key`-nøglen op.\n\n### Hvad API'et ikke kan endnu (fase 1, milepæl M6)\nPrismotoren SVARER nu — `/price_rules` og `/quotes` er bygget — men `availability` og\nbookingen bærer den endnu ikke: `price`, `price_from` og `price_explanation` er stadig tomme\nder, indtil linjen prissættes ved oprettelsen. · `/availability/explain` ·\n`/test/clock` · segment-editoren (fase 1 har præcis ét segment pr. ydelse, SPEC 0 #13).\n\n`/payments` er bygget: betalinger registreres manuelt (SPEC 6.14 — ingen gateway i fase 1),\nog `deposit_minor`/`deposit_due_at` beregnes ved bekræftelsen.\n\nBeskedreglerne SENDES nu: den interne planlægger (SPEC 1.4 — samme proces, ingen ekstern kø)\nkører sweeper og optioner hvert minut, beskeder hvert 5., integritetsscanneren hvert 10. og\nauto-gennemførsel hvert 15. `message_template` styrer tekst og timing.\n\n### Webhooks leverer nu\nOutboxen skrives i samme transaktion som bookingen, og en arbejder henter den hvert 10. sekund.\nSignatur `Lavet-Signature: t=…,v1=…` over `\"{t}.{raw_body}\"` — **verificér mod den rå body**,\nikke mod genparset JSON. Retry 10 s → 1 min → 5 min → 30 min → 2 t → 6 t → 12 t → 24 t med\njitter; `410 Gone` pauser straks; 72 timers uafbrudt fejl pauser og mailer. Max 5 parallelle\npr. endpoint, 10 sekunders timeout, hændelser gemmes i 30 dage.\n\n**Dubletter forekommer** (at-least-once). Dedupér på `Lavet-Event-Id` — den leveres som header,\nså I kan afvise før I parser. Der er INGEN rækkefølgegaranti: ordn efter `sequence` eller\n`data.object.version`. Var I nede, så hent ind med `GET /events?since_sequence=…`.","contact":{"name":"lavetsystem.dk","url":"https://lavetsystem.dk"},"license":{"name":"Proprietary","identifier":"LicenseRef-lavetsystem"}},"servers":[{"url":"https://api.lavetsystem.dk/v1","description":"Produktion"},{"url":"http://localhost:3000/api/v1","description":"Lokal udvikling"}],"tags":[{"name":"System","description":"Ping og dokumentation."},{"name":"Ledighed","description":"Beregnes altid, gemmes aldrig (SPEC 6.1 aksiom 1)."},{"name":"Hold","description":"Reservation under checkout — eget objekt, ikke en bookingstatus."},{"name":"Booking","description":"Oprettelse, læsning, rettelse og tilstandsovergange."},{"name":"Katalog","description":"Hvad der kan bookes og hvad der sælges."},{"name":"Penge","description":"Prisregler, prisopsplitning og fakturaer. Beløb er altid heltal i øre, og `amount_due` er afledt — aldrig gemt (SPEC 2.5)."},{"name":"Drift","description":"Beskeder, indstillinger, brugere, nøgler, webhooks og rapporter."},{"name":"Rapporter","description":"Fire spørgsmål med svar: hvor fuldt er huset, hvad tjener I og på hvad, hvad mister I og hvorfor, og hvem kommer igen. Alt er AFLEDT (SPEC §2.5, aksiom 1) — der findes ingen opsummeringstabel og intet natjob, så en rettelse i går er rettet i svaret nu. Bag modulet `reports`; `occupancy` er fase 1-listen og lever videre uden modulet."},{"name":"Vagtplan","description":"Vagter på personaleressourcer, bemandingsbehovet fra bookingerne, og godkendte timer ud til et lønsystem. Bag modulet `staffing`, som er slået FRA som standard. Modulet planlægger og godkender timer — det registrerer ikke tid og beregner ikke løn (SPEC §1.3)."}],"security":[{"bearerAuth":[]}],"paths":{"/ping":{"get":{"tags":["System"],"operationId":"ping","summary":"Er nøglen gyldig, og hvilken organisation peger den på?","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ping"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/openapi.json":{"get":{"tags":["System"],"operationId":"getOpenApi","summary":"Dette dokument. Offentligt, uden nøgle (SPEC 4.1 #7).","security":[],"responses":{"200":{"description":"OpenAPI 3.1-dokumentet."}}}},"/embed/bootstrap":{"get":{"tags":["System"],"operationId":"getEmbedBootstrap","summary":"Alt en widget skal bruge for at tegne første skærm — i ét kald","description":"SPEC §7 fase 2 (widget/embed), SPEC 4.10.\n\nWidget’en står i en `<iframe>` på kundens egen side og har ingen server til at gengive\nnoget for sig. Uden det her ville første maling koste fem runde ture. Svaret er en\nSAMLING af det de offentlige endpoints i forvejen siger — samme handlere, samme nøgle,\nsamme afgrænsning (aksiom 7) — ikke en genvej udenom dem.\n\n**Kun publishable nøgler** (`lspk_…`). En hemmelig nøgle får 403: den kan hente det hele\nenkeltvis, og et endpoint der svarer på begge, ville være et sted hvor en hemmelig nøgle\nkunne ende i en browser uden at nogen bemærkede det.\n\n`Lavet-Embed-Origin` er den adresse widget’en STÅR på, som browseren stemplede den\n(`event.origin` på håndtrykket). Har nøglen en origin-allowlist, håndhæves den mod den\nværdi — ellers ville listen være tom snak for netop den brugsmåde SPEC 4.2 nævner ved navn.","parameters":[{"name":"Lavet-Embed-Origin","in":"header","required":false,"schema":{"type":"string","examples":["https://mch.dk"]},"description":"Værtssidens adresse. Sættes af rammen, ikke af værten — og tælles kun når kaldet kom fra en side vi selv har serveret."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedBootstrap"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/availability":{"get":{"tags":["Ledighed"],"operationId":"getAvailability","summary":"Ledige tider — med begrundelse når de ikke er ledige","description":"SPEC 4.5, det vigtigste endpoint.\n\n`reasons` er ikke pynt. Det fjerner den mest almindelige supportsag i alle bookingsystemer\n(\"hvorfor kan jeg ikke booke tirsdag?\") og det er præcis det vores eget admin-UI viser i\nstedet for en grå celle.\n\nSvaret cacher med `ETag` + `Cache-Control: max-age=30`. Det er en HTTP-svarcache — der\nfindes ingen materialiseret ledighedstabel, og der kommer aldrig en (SPEC 0 #25).","parameters":[{"name":"offering_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"from","in":"query","schema":{"type":"string","format":"date-time"},"description":"Default: nu."},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Default: `from` + 7 dage."},{"name":"participants","in":"query","style":"deepObject","explode":true,"schema":{"type":"object","additionalProperties":{"type":"integer"}},"description":"Fx `participants[pca_adult]=40&participants[pca_child]=5`."},{"name":"pax","in":"query","schema":{"type":"integer","minimum":0},"description":"Kortform for ét anonymt hold."},{"name":"setup","in":"query","schema":{"type":"string"},"description":"Ønsket opstilling (`resource_setup.id`)."},{"name":"duration_minutes","in":"query","schema":{"type":"integer","minimum":1},"description":"Kræves når ydelsens `duration.mode` er `options` eller `customer`."},{"name":"include","in":"query","schema":{"type":"string","examples":["reasons"]},"description":"`reasons` er med som standard. `include=price` beregner `price` pr. slot for de viste dage og `price_from` pr. dag (SPEC 4.5) — kun for de første 120 ledige slots, ellers eksploderer et 90-dages kald. Sætter ingen regel en pris, udelades feltet helt: et 0 ville blive tegnet som \"gratis\"."},{"name":"channel","in":"query","schema":{"type":"string","enum":["public","admin"]},"description":"Hvilke øjne der kigger. `public` = en option blokerer (tærskel 1), `admin` = kun bekræftede blokerer (tærskel 2). Publishable-nøgler er altid `public` (SPEC 0 #3, 6.3)."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":2000,"default":500}}],"responses":{"200":{"description":"Dage og slots.","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Availability"}}}},"304":{"description":"Uændret siden `If-None-Match`."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/allocations":{"get":{"tags":["Ledighed"],"operationId":"listAllocations","summary":"Hvad der ALLEREDE optager ressourcerne i et vindue","description":"Kalenderens datakilde (SPEC 2.3, 5.3 #2). Additivt i forhold til SPEC 4.4's liste,\nog tilladt af SPEC 4.3.\n\n**Dette endpoint svarer ikke på om noget er ledigt.** Det returnerer de rækker der findes:\nbookinger, optioner, hold, lukkedage, vedligehold. Ingen slot-generering, ingen\nåbningstidsevaluering, ingen kravmatchning. Til \"kan dette bookes?\" er der `/availability`\nog serverens dom ved gem — de to er stadig den eneste sandhed (SPEC 6.1 aksiom 1).\n\nGrunden er målt: `/availability` bruger ~950 ms CPU for et 30-dages vindue, på den ene\nevent-loop. En kalender tegnet derfra ville lade én organisations tirsdag koste alle\nandre deres svartid. Dette er én indekseret forespørgsel med to left joins.\n\n`occupies` er det spærrede interval inkl. buffer, turnaround og omstilling — samme felt\nsom på bookingen (SPEC 4.6). Det er den skraverede hale i tidslinjen, og grunden til at\ningen skal genopfinde bufferberegningen.\n\nOverlapsprædikatet er SPEC 2.3's: `blocked_from < to AND from < blocked_to`. En buffer\nder rækker ind i vinduet kommer derfor med, selvom gæsteintervallet ligger udenfor.","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date-time"},"description":"RFC 3339 **med offset**. Default: nu."},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Default: `from` + 1 dag. Vinduet må højst være 62 dage."},{"name":"location_id","in":"query","schema":{"type":"string"}},{"name":"resource_id","in":"query","schema":{"type":"string"},"description":"Kommasepareret liste. Tom = alle ressourcer."},{"name":"min_block_level","in":"query","schema":{"type":"integer","minimum":0,"maximum":2,"default":1},"description":"2 = kun hårde spærringer · 1 = også optioner og hold (standard) · 0 = også frigivne rækker (aflyst, udløbet). SPEC 2.3: frigivelse sletter aldrig en række, den sætter `block_level = 0` — historikken skal kunne besvares et år senere."},{"name":"include_expired","in":"query","schema":{"type":"boolean","default":false},"description":"Udløbne hold og optioner spærrer ikke længere, og udelades derfor som standard (SPEC 6.5: udløb evalueres pr. forespørgsel mod serverens ur)."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":5000,"default":1000}},{"name":"starting_after","in":"query","schema":{"type":"string"},"description":"Cursor: id fra forrige sides `next_cursor` (SPEC 4.2)."}],"responses":{"200":{"description":"Allokeringer i vinduet, sorteret på `id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllocationList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/attention":{"get":{"tags":["Drift"],"operationId":"getAttention","summary":"Arbejdslisten: hvad kræver en beslutning, og hvad sker der i dag","description":"Forsidens datakilde (SPEC 5.3 skærm 1). Additivt i forhold til SPEC 4.4's liste, og\ntilladt af SPEC 4.3.\n\n**Ét kald i stedet for seks.** Skærmen åbnes tyve gange om dagen, ofte mens en kunde er\ni røret, og alternativet er seks forespørgsler — udløbende optioner, ubesvarede\nforespørgsler, manglende menuvalg, garantifrister, forfaldne fakturaer og tre dages\nprogram. Seks kald er seks autentificeringer og seks chancer for at ét af dem er det\nlangsomme.\n\n**Det svarer på hvad der ER, ikke på hvad der kunne bookes.** Ingen slot-generering,\ningen ledighedsberegning — samme afgrænsning som `/allocations` (BACKLOG #1). Hver\n`due_at` er en kolonne i basen, og hver handling skærmen tilbyder er et eksisterende\nverbum på `/bookings/{id}`. Tilstandsmaskinen (SPEC 6.8) er stadig det eneste sted en\nstatus flytter sig.\n\nHvornår en frist \"nærmer sig\" er husets eget svar: afstanden tages fra timingen på\nbeskedreglerne `booking.option_expiring` og `booking.guarantee_due` (SPEC 3 lag 4), så\nskærmen og brevet ikke kan være uenige om hvad \"snart\" betyder.\n\n`items` er sorteret med det overskredne først, derefter på frist. `today`/`tomorrow`\nrummer alt der OVERLAPPER dagen — et flerdagsarrangement står på hver af sine dage —\nmens `yesterday` rummer det der SLUTTEDE i går (SPEC 0 #19: rettes i syv dage).\n\nSvaret cacher ikke. En kalender der er ti sekunder gammel er stadig en kalender; en\narbejdsliste der stadig viser den option du lige har forlænget, er en liste der lyver.","parameters":[{"name":"date","in":"query","schema":{"type":"string","examples":["2026-07-25"]},"description":"Den LOKALE dag i husets tidszone — ikke et tidsstempel. Default: i dag. Det er det ene sted i API'et hvor en dato ikke er RFC 3339, fordi en dag ikke er et øjeblik (SPEC 6.10)."},{"name":"location_id","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"description":"Loft pr. liste. Et hus med tusind åbne forespørgsler må ikke vælte skærmen."}],"responses":{"200":{"description":"Arbejdslisten og de tre dages program.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Attention"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/waitlist":{"post":{"tags":["Booking"],"operationId":"createWaitlistEntry","summary":"Skriv nogen på ventelisten","description":"SPEC §7 fase 2 · modulet `waitlist` (SPEC §3 lag 5). Er modulet slået fra, svarer\nendpointet 404 med en sætning om hvor det tændes — det findes ikke, det er ikke låst.\n\n**Et ønske er ikke en booking.** Det bærer et INTERVAL af mulige starttidspunkter\n(`window_from`/`window_to`), ikke ét tidspunkt, og det spærrer ingenting. Bliver en tid\nfri inde i vinduet, får den der har ventet længst — og som ydelsen faktisk kan tage —\net tilbud pr. mail med et signeret link og en frist (standard 4 timer, husets eget tal\ni `waitlist_offer_hours`). Ét aktivt tilbud pr. frigivet tid; ingen kapløb.\n\n`contact_email` er påkrævet: tilbuddet ER en mail.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Understøttet, ikke krævet: to ens tilmeldinger fra den samme familie er ikke en fejl."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["offering_id","pax","window_from","window_to","contact_name","contact_email"],"properties":{"offering_id":{"type":"string"},"pax":{"type":"integer","minimum":1},"window_from":{"type":"string","format":"date-time"},"window_to":{"type":"string","format":"date-time"},"contact_name":{"type":"string"},"contact_email":{"type":"string"},"contact_phone":{"type":"string","nullable":true},"customer_id":{"type":"string","nullable":true},"setup_id":{"type":"string","nullable":true},"note":{"type":"string","nullable":true}}}}}},"responses":{"201":{"description":"Ønsket står på listen."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited`: for mange kald pr. nøgle (SPEC 4.2). `Retry-After` siger hvornår.\n\n`too_many_holds`: loftet på aktive reservationer er nået (SPEC 6.5). Det er IKKE en\nthrottling — at vente hjælper ikke. Gør et hold færdigt, eller giv det fri med\n`DELETE /slot_holds/{id}`. `conflict.max_active_holds` siger hvad loftet er, og\n`conflict.scope` om det blev ramt pr. session eller pr. IP.\n\nMulige `code`-værdier: `rate_limited`, `too_many_holds`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Booking"],"operationId":"listWaitlist","summary":"Ventelisten","description":"Standard er den LEVENDE liste (`waiting`, `offered`). `status` tager en kommasepareret\nliste hvis man vil se det der er blevet til bookinger.\n\nHver række bærer sit åbne tilbud med frist, når der er et — listen skal kunne svare på\n\"hvem venter vi på\" uden et ekstra kald.","parameters":[{"name":"status","in":"query","schema":{"type":"string"}},{"name":"offering_id","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Ønskerne, ældste først."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/waitlist/entries/{id}":{"delete":{"tags":["Booking"],"operationId":"withdrawWaitlistEntry","summary":"Tag et ønske af listen","description":"Sletter ikke (SPEC 2.6 #6): ønsket får `cancelled` og et `archived_at`, og et åbent\ntilbud på det trækkes med — så den frigivne tid straks kan gå videre til den næste i\nkøen frem for at vente på en frist ingen svarer på.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Ønsket er taget af."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/waitlist/accept":{"post":{"tags":["Booking"],"operationId":"acceptWaitlistOfferByToken","summary":"Sig ja til et ventelistetilbud med det signerede link","description":"Ingen legitimation: **tokenet ER legitimationen**, signeret med husets egen\nhemmelighed og bundet til ét tilbud (SPEC 5.3 #5 — ingen konto, aldrig).\n\nAccepten går gennem den normale skrivevej med fuld genvalidering (aksiom 2): et tilbud\nspærrer ingenting, så tiden KAN være blevet solgt mens fristen løb. Sker det, svarer\nkaldet 409 `slot_unavailable` med motorens egne grunde og de nærmeste ledige tider.\nEr tilbuddet udløbet eller besvaret, er svaret 409 `hold_expired`.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","examples":["lswt_…"]}}}}}},"responses":{"201":{"description":"Bookingen er oprettet."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited`: for mange kald pr. nøgle (SPEC 4.2). `Retry-After` siger hvornår.\n\n`too_many_holds`: loftet på aktive reservationer er nået (SPEC 6.5). Det er IKKE en\nthrottling — at vente hjælper ikke. Gør et hold færdigt, eller giv det fri med\n`DELETE /slot_holds/{id}`. `conflict.max_active_holds` siger hvad loftet er, og\n`conflict.scope` om det blev ramt pr. session eller pr. IP.\n\nMulige `code`-værdier: `rate_limited`, `too_many_holds`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/waitlist/offers/{id}/accept":{"post":{"tags":["Booking"],"operationId":"acceptWaitlistOffer","summary":"Huset siger ja på kundens vegne","description":"Kunden ringer i stedet for at klikke. Samme funktion som linket rammer (aksiom 7); kun legitimationen er en anden, og bookingens `source` bliver `admin`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"responses":{"201":{"description":"Bookingen er oprettet."},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/booking_series":{"post":{"tags":["Booking"],"operationId":"createBookingSeries","summary":"Gentagne bookinger — «hver tirsdag 10–12, 12 gange»","description":"SPEC §2.6 #7, §7 fase 2. RRULE-delmængden er lukket: `FREQ` ∈ {WEEKLY, MONTHLY},\n`INTERVAL`, og enten `COUNT` eller `UNTIL`. Ugedag, dag-i-måneden og klokkeslæt kommer\nfra `start` — den første forekomst er ankeret.\n\n**Svaret er en rapport, ikke et objekt.** Kaldet kan lykkes DELVIST: hver dato i\n`occurrences` siger ja eller nej, og et nej bærer motorens egne `reasons` og de nærmeste\nledige tider, så en enkelt fejlet dato kan løses med ét klik (SPEC 4.7 — aldrig en\nblindgyde). `201` når mindst én dato blev til noget, `409` når ingen gjorde.\n\nForekomsterne materialiseres ved oprettelsen: en booking der først findes når nogen\nkigger på kalenderen, er en booking der ikke kan spærre et lokale. Hver enkelt går\ngennem den samme `commitBooking` som en enkeltstående booking — der findes ingen\nserie-vej gennem motoren.\n\nDatoer der ikke findes (den 31. i en 30-dages måned, timen der springes over om\nforåret) står i rapporten med `skipped` og en dansk sætning. De bruger deres plads i\n`COUNT`: man bad om tolv gange fra mønstret, og rapporten viser hvad de tolv blev til.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["offering_id","start","recurrence"],"properties":{"offering_id":{"type":"string"},"start":{"type":"string","format":"date-time"},"pax":{"type":"integer","minimum":0},"status":{"type":"string","examples":["confirmed","option"]},"duration_minutes":{"type":"integer","minimum":1},"setup_id":{"type":"string","nullable":true},"recurrence":{"type":"object","required":["freq"],"properties":{"freq":{"type":"string","enum":["WEEKLY","MONTHLY"]},"interval":{"type":"integer","minimum":1,"maximum":52,"default":1},"count":{"type":"integer","minimum":1,"maximum":104,"nullable":true},"until":{"type":"string","format":"date-time","nullable":true}}}}}}}},"responses":{"201":{"description":"Serien er oprettet. Rapporten siger hvilke datoer der blev til bookinger."},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/booking_series/{id}":{"get":{"tags":["Booking"],"operationId":"getBookingSeries","summary":"Reglen og de forekomster den har lavet","description":"Hver forekomst bærer `detached`. Uden det felt kan hverken en skærm eller en integrator se hvad «aflys serien» ville røre — og det er lige præcis dét spørgsmål man stiller inden man trykker.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Serien."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/booking_series/{id}/cancel":{"post":{"tags":["Booking"],"operationId":"cancelBookingSeries","summary":"Aflys serien","description":"SPEC §2.6 #7: *«Aflys serie = aflys fremtidige ikke-detachede.»* Afviklede forekomster\nlaves ikke om, og en forekomst nogen har løsrevet (`/bookings/{id}/detach`) er sin egen\naftale nu og røres ikke.\n\nHver enkelt går gennem tilstandsmaskinens `cancel` med vilkår, gebyrer og journaler —\nikke gennem en `UPDATE`. En der ikke kunne aflyses, står i `refused` med sin egen\nsætning og stopper ikke de andre.\n\n`from` er «denne og fremtidige»-grænsen. Uden den gælder det hele serien fra nu.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","format":"date-time","nullable":true},"reason":{"type":"string","nullable":true},"cancel_reason_id":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Hvad der blev aflyst, og hvad der ikke kunne."},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/detach":{"post":{"tags":["Booking"],"operationId":"detachBookingFromSeries","summary":"Kun denne — løsriv én forekomst fra sin serie","description":"SPEC §2.6 #7. Svaret på det spørgsmål skærmen stiller når nogen retter én forekomst:\n*kun denne* eller *denne og fremtidige*. Vælges den første, løsrives forekomsten FØR\nrettelsen — rækkefølgen er hele garantien: løsrives den bagefter, findes der et øjeblik\nhvor en samtidig «aflys serien» stadig kan tage den med.\n\nIdempotent. En booking uden serie giver 422 — der er ikke noget at løsrive den fra, og\net stille «ok» ville skjule at skærmen kaldte det forkerte endpoint.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Forekomsten følger ikke serien længere."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/slot_holds":{"post":{"tags":["Hold"],"operationId":"createSlotHold","summary":"Reservér et slot under checkout","description":"SPEC 0 #4, 6.5. Et hold er sit EGET objekt: ingen bookingrække, intet referencenummer,\ningen status. Statusserne `draft` og `hold` findes ikke — en travl F&B-ansvarlig må aldrig\nse 23 forladte checkouts i sin bookingliste.\n\nHoldet spærrer hårdt (`block_level = 2`) indtil `expires_at`. Udløb evalueres i hver\nforespørgsel på serverens ur, så korrektheden aldrig afhænger af at oprydningsjobbet kører.\n\nVarigheden er ydelsens `hold_ttl_min` (SPEC 6.5, standard 10 minutter) med mindre\n`ttl_minutes` sendes med. Der må være højst `max_active_holds` (standard 3) LEVENDE hold\nad gangen — talt både pr. `session_key` og pr. afsender-IP, fordi en session_key er noget\nkalderen selv finder på. Over loftet: 429 `too_many_holds`, som ikke går væk af at vente.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlotHoldCreate"}}}},"responses":{"201":{"description":"Holdet er oprettet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlotHold"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited`: for mange kald pr. nøgle (SPEC 4.2). `Retry-After` siger hvornår.\n\n`too_many_holds`: loftet på aktive reservationer er nået (SPEC 6.5). Det er IKKE en\nthrottling — at vente hjælper ikke. Gør et hold færdigt, eller giv det fri med\n`DELETE /slot_holds/{id}`. `conflict.max_active_holds` siger hvad loftet er, og\n`conflict.scope` om det blev ramt pr. session eller pr. IP.\n\nMulige `code`-værdier: `rate_limited`, `too_many_holds`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/slot_holds/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Hold"],"operationId":"getSlotHold","summary":"Hent et hold","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlotHold"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Hold"],"operationId":"extendSlotHold","summary":"Forlæng holdet (heartbeat)","description":"SPEC 6.5: én gang, og aldrig ud over det dobbelte af den oprindelige længde. Ellers spærrer et åbent faneblad en lørdag i timevis.","responses":{"200":{"description":"Forlænget.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlotHold"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Hold"],"operationId":"releaseSlotHold","summary":"Frigiv holdet","description":"SPEC 2.3: frigivelse SLETTER aldrig en allokeringsrække — den sætter `block_level = 0`, så \"hvorfor var lokalet optaget den dag\" kan besvares et år senere.","responses":{"200":{"description":"Frigivet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlotHold"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings":{"post":{"tags":["Booking"],"operationId":"createBooking","summary":"Opret en booking","description":"SPEC 4.4, 4.8, 6.1 aksiom 2.\n\nKlientens valgte tidspunkt er et **forslag**. Serveren kører hele valideringen igen —\nåbningstid, undtagelser, varsel, horisont, kapacitet, konflikt, pax — inde i den\n`BEGIN IMMEDIATE`-transaktion der skriver. Er slottet taget i mellemtiden, kommer der en\n409 med `alternatives`.\n\n`Idempotency-Key` er obligatorisk. `external_ref` er andet lag: den er unik pr.\norganisation for altid, så et gensendt kald efter 24-timers-vinduet giver `200 OK` med den\neksisterende booking i stedet for dublet nummer to (SPEC 4.8).","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingCreate"}}}},"responses":{"200":{"description":"Bookingen fandtes allerede via `external_ref` (SPEC 4.8, andet lag).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"201":{"description":"Oprettet.","headers":{"ETag":{"description":"Objektets `version`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Booking"],"operationId":"listBookings","summary":"Liste med cursor-paginering","description":"`?updated_since=` er den dokumenterede polling-fallback til webhooks (SPEC 4.4 \"Drift\").\nRækkefølgen er `id` (ULID = oprettelsesrækkefølge), så en cursor forbliver gyldig mens\nbookinger opdateres under en klient der bladrer.\n\n**`?custom[<nøgle>]=<værdi>` filtrerer på husets egne felter** (SPEC §3 lag 2). Det virker\nkun for felter der er markeret `indexed`, fordi kun de projiceres til `field_value` ved\nskrivning. Et felt der ikke er det, giver 422 med listen over dem der er — at svare\n«ingen fundet» på data der findes ville se ud som et rigtigt svar (SPEC 4.7).\n\nSamme nøgle flere gange er ELLER (`?custom[status]=a&custom[status]=b`); forskellige\nnøgler er OG. Målt på 20.000 bookinger: 12,4 ms med `json_extract`, 0,01 ms via\n`field_value`.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"starting_after","in":"query","schema":{"type":"string"},"description":"Cursor: id fra forrige sides `next_cursor` (SPEC 4.2)."},{"name":"custom[key]","in":"query","schema":{"type":"string"},"description":"Fx `?custom[ean]=5798000416666`. Kun felter markeret `indexed` (SPEC §3 lag 2)."},{"name":"updated_since","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"status","in":"query","schema":{"type":"string"},"description":"Kommasepareret."},{"name":"location_id","in":"query","schema":{"type":"string"}},{"name":"customer_id","in":"query","schema":{"type":"string"}},{"name":"external_ref","in":"query","schema":{"type":"string"}},{"name":"reference","in":"query","schema":{"type":"string"}},{"name":"source","in":"query","schema":{"type":"string","enum":["public","admin","api","import"]}},{"name":"offering_id","in":"query","schema":{"type":"string"},"description":"Bookinger med mindst én linje på den ydelse. `EXISTS`, ikke join — en booking med tre linjer på samme ydelse står én gang."},{"name":"resource_id","in":"query","schema":{"type":"string"},"description":"Bookinger der optager ressourcen — gennem `resource_conflict` (SPEC §2.4), så en booking på Sal A også findes under Sal A+B. Det er den samme lukning belægningsrapporten regner på, og de to skal være enige."},{"name":"cancel_reason_id","in":"query","schema":{"type":"string"},"description":"Vejen fra et tal i aflysningsrapporten til rækkerne bag det."},{"name":"start_gte","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"start_lt","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"expand","in":"query","schema":{"type":"string"},"description":"Fase 1: `customer`, `offering`, `resources` (SPEC 4.2)."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Booking"],"operationId":"getBooking","summary":"Hent en booking","parameters":[{"name":"expand","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Objektets `version`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Booking"],"operationId":"updateBooking","summary":"Ret felter på en booking","description":"SPEC 4.4: **tilstandsovergange er verber, ikke `PATCH status`.** En PATCH kan ikke bære\naflysningsårsag, refusionspolitik og \"underret kunden\"-flag uden at blive et misbrugt felt.\n\n`status`, `start`, `end`, `pax`, `offering_id` og `setup_id` afvises derfor med 422.\nFlytning er en egen transaktion (SPEC 6.11) og hører til en senere milepæl.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingPatch"}}}},"responses":{"200":{"description":"Opdateret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/confirm":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"confirmBooking","summary":"Bekræft bookingen","description":"SPEC 4.4: tilstandsovergange er **verber**, ikke `PATCH status`.\n\nSPEC 6.8. Konvertering af en option kører **samme fulde validering** som en ny booking —\ni mellemtiden kan en `confirmed` være landet på ressourcen. Sker det, kommer der en 409\n`slot_unavailable` med `conflict`.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."},{"name":"If-Match","in":"header","required":false,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":["string","null"]},"cancel_reason_id":{"type":["string","null"],"description":"Fra den redigerbare liste (SPEC 6.11)."},"cancelled_by":{"type":["string","null"],"enum":["customer","venue","system",null]},"grace_minutes":{"type":"integer","minimum":0,"description":"Kun `no_show` (SPEC 6.8)."},"override_reason":{"type":["string","null"]},"notify":{"type":"boolean","description":"Kun `cancel` har et brev i dag. Standard: `true` når legitimationen er et gæstelink, ellers `false` (SPEC 5.3)."}}}}}},"responses":{"200":{"description":"Overgangen er gennemført.","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/cancel":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"cancelBooking","summary":"Aflys bookingen","description":"SPEC 4.4: tilstandsovergange er **verber**, ikke `PATCH status`.\n\nSPEC 2.3: aflysning SLETTER aldrig en allokeringsrække — den sætter `block_level = 0`.\nSPEC 6.11: `cancel_reason_id` peger på den redigerbare liste over aflysningsårsager, som er\nhele grundlaget for rapporten \"hvorfor mister vi bookinger\".","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."},{"name":"If-Match","in":"header","required":false,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":["string","null"]},"cancel_reason_id":{"type":["string","null"],"description":"Fra den redigerbare liste (SPEC 6.11)."},"cancelled_by":{"type":["string","null"],"enum":["customer","venue","system",null]},"grace_minutes":{"type":"integer","minimum":0,"description":"Kun `no_show` (SPEC 6.8)."},"override_reason":{"type":["string","null"]},"notify":{"type":"boolean","description":"Kun `cancel` har et brev i dag. Standard: `true` når legitimationen er et gæstelink, ellers `false` (SPEC 5.3)."}}}}}},"responses":{"200":{"description":"Overgangen er gennemført.","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/no_show":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"noShowBooking","summary":"Markér udeblivelse bookingen","description":"SPEC 4.4: tilstandsovergange er **verber**, ikke `PATCH status`.\n\nSPEC 6.8 [H]: `no_show` er **aldrig** en variant af `cancelled` — rapporterne skal kunne\nskelne. Kan først sættes efter `ends_at` (eller `starts_at + grace_minutes`), og systemet må\nkun **foreslå** det; et menneske sætter det.\n\nUden `grace_minutes` i kaldet bruges husets egen tålmodighedsfrist —\n`tenant_setting.no_show_grace_min`, i minutter fra starttidspunktet. Er den ikke sat,\ngælder `ends_at`. Et forsøg før tid giver 422 med den tidligste lovlige dato i klar tekst.\n\nVerbet dækker to overgange: `confirmed → no_show` og **rettelsen** `completed → no_show`,\nsom er tilladt i 7 dage efter `ends_at`. Uden den ville alt hvad natjobbet nåede at kalde\n\"gennemført\" være låst som gennemført — og SPEC 6.8 [H] handler netop om at rapporterne\nskal kunne skelne. Rettelsen skriver sin egen begrundelse i `audit_log`.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."},{"name":"If-Match","in":"header","required":false,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":["string","null"]},"cancel_reason_id":{"type":["string","null"],"description":"Fra den redigerbare liste (SPEC 6.11)."},"cancelled_by":{"type":["string","null"],"enum":["customer","venue","system",null]},"grace_minutes":{"type":"integer","minimum":0,"description":"Kun `no_show` (SPEC 6.8)."},"override_reason":{"type":["string","null"]},"notify":{"type":"boolean","description":"Kun `cancel` har et brev i dag. Standard: `true` når legitimationen er et gæstelink, ellers `false` (SPEC 5.3)."}}}}}},"responses":{"200":{"description":"Overgangen er gennemført.","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/complete":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"completeBooking","summary":"Markér gennemført bookingen","description":"SPEC 4.4: tilstandsovergange er **verber**, ikke `PATCH status`.\n\nSPEC 6.8, beslutning #19: en bekræftet booking bliver **automatisk** `completed` 24 timer\nefter `ends_at` — medmindre den i mellemtiden er markeret `no_show`, `cancelled` eller\n`late_released` (så er den ikke længere `confirmed`, og jobbet ser den ikke).\n\nDette verbum er den manuelle halvdel: aftenen er ovre, og personalet vil ikke vente til i\nmorgen. Begge halvdele afvises før `ends_at` med 422.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."},{"name":"If-Match","in":"header","required":false,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":["string","null"]},"cancel_reason_id":{"type":["string","null"],"description":"Fra den redigerbare liste (SPEC 6.11)."},"cancelled_by":{"type":["string","null"],"enum":["customer","venue","system",null]},"grace_minutes":{"type":"integer","minimum":0,"description":"Kun `no_show` (SPEC 6.8)."},"override_reason":{"type":["string","null"]},"notify":{"type":"boolean","description":"Kun `cancel` har et brev i dag. Standard: `true` når legitimationen er et gæstelink, ellers `false` (SPEC 5.3)."}}}}}},"responses":{"200":{"description":"Overgangen er gennemført.","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/quote":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"quoteBooking","summary":"Send tilbud på bookingen","description":"SPEC 4.4: tilstandsovergange er **verber**, ikke `PATCH status`.\n\nSPEC 6.8 + 6.13: `inquiry → quoted`, anden station på sporet *\"Forespørgsel → tilbud →\noption → bekræftelse → EAN-faktura\"*.\n\n`quoted` har `block_level = 0`. Et tilbud optager sælgeren, aldrig lokalet — skal datoen\nsikres mens kunden tænker, er det `POST /bookings/{id}/option`.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."},{"name":"If-Match","in":"header","required":false,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":["string","null"]},"cancel_reason_id":{"type":["string","null"],"description":"Fra den redigerbare liste (SPEC 6.11)."},"cancelled_by":{"type":["string","null"],"enum":["customer","venue","system",null]},"grace_minutes":{"type":"integer","minimum":0,"description":"Kun `no_show` (SPEC 6.8)."},"override_reason":{"type":["string","null"]},"notify":{"type":"boolean","description":"Kun `cancel` har et brev i dag. Standard: `true` når legitimationen er et gæstelink, ellers `false` (SPEC 5.3)."}}}}}},"responses":{"200":{"description":"Overgangen er gennemført.","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/option":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"optionBooking","summary":"Læg en option på bookingen","description":"SPEC 4.4: tilstandsovergange er **verber**, ikke `PATCH status`.\n\nSPEC 6.8 [T]: `inquiry → option` (\"hus lægger tentativt hold\") og `quoted → option`\n(\"kunde beder om hold\"). Bookingen går til `block_level = 1`: den lukker den offentlige\nflade, men hverken admin eller andre optioner.\n\n**Hverken frist eller rang skal sendes med.** `option_expires_at` udledes af\n`offering.option_hold_hours`, ellers `tenant_setting.option_hold_days`, ellers 14 dage\n(SPEC 8 #2), og `hold_rank` beregnes som *1 + antallet af ældre optioner der faktisk\nkæmper om den samme ressource i det samme vindue*. En option uden frist ville aldrig\nudløbe, og to overlappende optioner ville begge kalde sig 1. option.\n\nOvergangen kører **samme fulde validering** som en ny booking. Var forespørgslen skrevet\nned på et optaget tidspunkt (den har da ingen allokeringer), oprettes de her — eller\nafvises med 409 `slot_unavailable`.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."},{"name":"If-Match","in":"header","required":false,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":["string","null"]},"cancel_reason_id":{"type":["string","null"],"description":"Fra den redigerbare liste (SPEC 6.11)."},"cancelled_by":{"type":["string","null"],"enum":["customer","venue","system",null]},"grace_minutes":{"type":"integer","minimum":0,"description":"Kun `no_show` (SPEC 6.8)."},"override_reason":{"type":["string","null"]},"notify":{"type":"boolean","description":"Kun `cancel` har et brev i dag. Standard: `true` når legitimationen er et gæstelink, ellers `false` (SPEC 5.3)."}}}}}},"responses":{"200":{"description":"Overgangen er gennemført.","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/decline":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"declineBooking","summary":"Markér afvist bookingen","description":"SPEC 4.4: tilstandsovergange er **verber**, ikke `PATCH status`.\n\nSPEC 6.8: `inquiry → declined` og `quoted → declined` — *\"kunde takker nej\"*.\n\nEn egen kode, ikke en variant af `cancelled`: en forespørgsel der aldrig blev til en\nbooking er ikke en tabt booking, og SPEC 6.11s rapport \"hvorfor mister vi bookinger\" er\nkun værd at læse hvis de to kan skelnes. `cancel_reason_id` gælder også her.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."},{"name":"If-Match","in":"header","required":false,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":["string","null"]},"cancel_reason_id":{"type":["string","null"],"description":"Fra den redigerbare liste (SPEC 6.11)."},"cancelled_by":{"type":["string","null"],"enum":["customer","venue","system",null]},"grace_minutes":{"type":"integer","minimum":0,"description":"Kun `no_show` (SPEC 6.8)."},"override_reason":{"type":["string","null"]},"notify":{"type":"boolean","description":"Kun `cancel` har et brev i dag. Standard: `true` når legitimationen er et gæstelink, ellers `false` (SPEC 5.3)."}}}}}},"responses":{"200":{"description":"Overgangen er gennemført.","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/late_release":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"lateReleaseBooking","summary":"Markér plads givet væk bookingen","description":"SPEC 4.4: tilstandsovergange er **verber**, ikke `PATCH status`.\n\nSPEC 6.8s tredje afslutningskode: *\"kom 45 min for sent, bordet var givet væk — lyder som\noverkill indtil man skal forklare en regning.\"*\n\nHverken `no_show` (de KOM) eller `cancelled` (ingen aflyste). Kun et menneske må sætte den,\nog tidligst når arrangementet er **begyndt** — man kommer ikke for sent til noget der ikke\ner gået i gang. Event: `booking.late_released`.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."},{"name":"If-Match","in":"header","required":false,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":["string","null"]},"cancel_reason_id":{"type":["string","null"],"description":"Fra den redigerbare liste (SPEC 6.11)."},"cancelled_by":{"type":["string","null"],"enum":["customer","venue","system",null]},"grace_minutes":{"type":"integer","minimum":0,"description":"Kun `no_show` (SPEC 6.8)."},"override_reason":{"type":["string","null"]},"notify":{"type":"boolean","description":"Kun `cancel` har et brev i dag. Standard: `true` når legitimationen er et gæstelink, ellers `false` (SPEC 5.3)."}}}}}},"responses":{"200":{"description":"Overgangen er gennemført.","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/option/extend":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"extendBookingOption","summary":"Forlæng optionen","description":"SPEC 4.4: tilstandsovergange er **verber**, ikke `PATCH status` — og en option er\ningen undtagelse: en frist, en rang og en forfremmelse kan ikke smugles gennem et felt.\n\nSPEC 6.8: en option lever i **dage/uger** med et udløb. Forlængelsen regnes fra den\nnuværende frist når den stadig er i fremtiden, ellers fra nu — \"forlæng med 7 dage\" på en\noption der løb ud i går betyder en uge fra i dag, ikke seks dage.\n\n`hold_rank` røres ikke. En forlængelse er ikke en ny option, og en omrangering ville\nflytte husets 1. option om bag en der spurgte senere.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."},{"name":"If-Match","in":"header","required":false,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["days"],"properties":{"days":{"type":"integer","minimum":1,"maximum":365,"description":"Hele dage."},"reason":{"type":["string","null"]}}}}}},"responses":{"200":{"description":"Optionen er ændret.","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/option/challenge":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"challengeBookingOption","summary":"Udfordr optionen","description":"SPEC 4.4: tilstandsovergange er **verber**, ikke `PATCH status` — og en option er\ningen undtagelse: en frist, en rang og en forfremmelse kan ikke smugles gennem et felt.\n\nSPEC 6.8 [T], den F&B-kritiske regel. Vil en senere option (eller en kunde i røret) have\ndatoen, **udfordres** den siddende option: indehaveren får en frist (default 48 timer) til\nat bekræfte eller frigive. Svarer de ikke, frigives optionen automatisk og rangen rykker op.\n\nFristen står i `challenge_due_at` på bookingen. Event: `booking.option_challenged` — og\n`booking.option_released`, hvis fristen løber ud.\n\nEn allerede udfordret option kan ikke udfordres igen mens fristen løber (409): ellers\nkunne indehaveren få 48 nye timer hver gang nogen trykkede på knappen.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."},{"name":"If-Match","in":"header","required":false,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"challenger_booking_id":{"type":["string","null"],"description":"Bookingen der vil have datoen. Valgfri — rivalen sidder ofte i telefonen."},"deadline_hours":{"type":"integer","minimum":1,"maximum":720,"description":"Frist i timer. Standard 48 (SPEC 6.8)."}}}}}},"responses":{"200":{"description":"Optionen er ændret.","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/option/release":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"releaseBookingOption","summary":"Frigiv optionen","description":"SPEC 4.4: tilstandsovergange er **verber**, ikke `PATCH status` — og en option er\ningen undtagelse: en frist, en rang og en forfremmelse kan ikke smugles gennem et felt.\n\nOptionen slippes — frivilligt eller efter en tabt udfordring. Bookingen ender i\n`cancelled` gennem den samme `transition()` som alt andet (SPEC 2.3: allokeringsrækkerne\nsættes til `block_level = 0`, de slettes aldrig), og **kun** de optioner der faktisk\nkæmpede om den samme ressource i det samme vindue rykker op.\n\nEvent: `booking.option_released` + `slot.released`.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."},{"name":"If-Match","in":"header","required":false,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":["string","null"]}}}}}},"responses":{"200":{"description":"Optionen er ændret.","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/reschedule":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"rescheduleBooking","summary":"Flyt bookingen til et nyt tidspunkt","description":"SPEC 6.11. Flytning er **ikke** `PATCH start` — `PATCH` afviser feltet ved navn.\n\nI ÉN transaktion: validér det nye slot fuldt ud → sæt de gamle allokeringer til\n`block_level = 0` → opret de nye → bump `version` → skriv `booking.rescheduled`.\nRækkefølgen er bærende: der findes intet øjeblik hvor gæsten hverken har det gamle\neller det nye slot. Fejler valideringen, rulles alt tilbage og bookingen står urørt.\nAldrig \"slet først, book bagefter\".\n\nDette er verbet bag SPEC 5.4's ét-kliks flytning i tidslinjen. Send `If-Match` med den\nversion du tegnede, så to der trækker i samme blok ender i 412 med det aktuelle objekt\nvedhæftet — ikke i sidste-skriver-vinder.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."},{"name":"If-Match","in":"header","required":false,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["start"],"properties":{"start":{"type":"string","format":"date-time","description":"Nyt starttidspunkt, RFC 3339 med offset."},"duration_minutes":{"type":"integer","minimum":1,"description":"Kun når flytningen også ændrer længden (træk i blokkens kant)."},"pax":{"type":"integer","minimum":0},"participants":{"type":"object","additionalProperties":{"type":"integer"}},"setup_id":{"type":["string","null"]},"preferred":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}},"description":"Kravnøgle → ressourcer der prøves først (SPEC 6.3)."},"reason":{"type":["string","null"]},"guarantee_override_reason":{"type":"string","minLength":3,"description":"SPEC 6.13 [T]: et deltagerantal over garantitolerancen afvises med 422 og det tal der VILLE gå igennem. Dette er personalet der siger ja alligevel. Droppes stille fra et gæstelink."},"override_reason":{"type":"string","minLength":3,"description":"SPEC 6.7 [S]: bufferkonflikten, tilsidesat af et navngivet menneske. Send kaldet igen med dette felt efter en `409 buffer_conflict` med `overridable: true`. Droppes stille fra et gæstelink."},"notify":{"type":"boolean","description":"Send flytte-mailen til gæsten. Standard: `true` når legitimationen er et gæstelink (gæsten har selv trykket), ellers `false` — SPEC 5.3: systemet sender aldrig en mail bag ryggen på nogen."}}}}}},"responses":{"200":{"description":"Flyttet.","headers":{"ETag":{"description":"Den nye `version`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/calendar.ics":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Booking"],"operationId":"bookingCalendarFile","summary":"Bookingen som kalenderfil (.ics)","description":"SPEC 4.4, 6.10. `DTSTART;TZID=Europe/Copenhagen` med **indlejret VTIMEZONE**, ikke UTC-Z\nalene: en aftale er et vægur, ikke et øjeblik, og Outlook håndterer flytninger pænere.\nOvergangene udledes af IANA-zonen — ingen hardkodet EU-regel (samme argument som\nhelligdagene i SPEC 6.9).\n\n`SEQUENCE` er bookingens `version`, så en genudsendt fil OPDATERER aftalen i gæstens\nkalender i stedet for at lægge en ny ved siden af.\n\nDette er det ENESTE endpoint der tager sin legitimation fra query-strengen, og kun\ngæstens `lsbt_`-link: en URL i Outlook kan ikke bære en header. SPEC 4.2 forbyder\nAPI-nøgler i query-strengen, og det står uændret — en `lsk_`/`lspk_`-nøgle her er ingenting.","parameters":[{"name":"token","in":"query","schema":{"type":"string"},"description":"Gæstens signerede link-token (`lsbt_…`). Udelades når kaldet bærer en session eller en nøgle."}],"responses":{"200":{"description":"iCalendar-filen.","content":{"text/calendar":{"schema":{"type":"string"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/search":{"get":{"tags":["System"],"operationId":"search","summary":"Én søgelinje til alt","description":"SPEC 5.5 #9: \"Én søgelinje til alt. ⌘K søger i bookinger, kunder, telefonnumre,\nydelser, lokaler og handlinger.\" Additivt i forhold til SPEC 4.4's liste (SPEC 4.3).\n\nHandlinger er klientens ansvar — den ved hvad der er på skærmen. Alt der kommer fra\ndatabasen kommer herfra, så admin-paletten og en integrators egen søgefelt ser de\nsamme rækker gennem de samme scopes (aksiom 7).\n\n**Linjen må bære en dato.** SPEC 5.4 budgetterer \"Se om Sal A er ledig 3. oktober\"\ntil to klik (⌘K → `sal a 3/10`), hvilket kræver at noget deler linjen i en ting og\nen dag. Det gør serveren, og fortolkningen kommer med i svarets `query`-objekt\n(`text`, `date`, `date_text`), så alle kaldere forstår `3/10` ens. Datoen læses i\nHUSETS kalender (SPEC 6.10). Genkendte former: `3/10` · `3-10` · `3/10-27` ·\n`2026-10-03` · `3. oktober` · `3 okt` · `i dag` · `i morgen` · `overmorgen` ·\n`mandag` … `søndag`.\n\nResultatet indsnævres til de typer nøglen må læse, i stedet for at blive afvist:\nen publishable-nøgle med kun `catalog:read` får ydelser og ressourcer, og får aldrig\nat vide at der findes kunder. Telefonnumre sammenlignes på cifre alene.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2}},{"name":"types","in":"query","schema":{"type":"string"},"description":"Kommasepareret: `booking`, `customer`, `offering`, `resource`."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":25,"default":5},"description":"Pr. type."}],"responses":{"200":{"description":"Træf på tværs af typer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResultList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sessions":{"post":{"tags":["System"],"operationId":"createSession","summary":"Log ind — den anden legitimation ind i de samme handlers","description":"SPEC 4.1 #1 / aksiom 7: admin-UI'et er ikke et lag oven på API'et, det er en anden\n**legitimation** ind i de samme route-handlers. Et login veksler e-mail + adgangskode\ntil en session-cookie der giver præcis samme `AuthContext` som en Bearer-nøgle.\n\nTo cookies sættes: `les_session` (httpOnly) og `les_csrf` (læsbar). Mutationer med\ncookie-legitimation skal sende CSRF-tokenet som `X-CSRF-Token` — dobbelt-submit plus\nOrigin-tjek. Bearer-kald er upåvirkede: en nøgle kan ikke afspilles af en fremmed side.\n\nAccepterer både JSON og `application/x-www-form-urlencoded`, så login virker uden\nJavaScript — og svarer da 303 i stedet for 200, så legitimationen aldrig havner i\nadressefeltet eller browserhistorikken.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string"}}}}}},"responses":{"200":{"description":"Logget ind. Cookies er sat."},"303":{"description":"Logget ind via formular-post; browseren sendes videre."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited`: for mange kald pr. nøgle (SPEC 4.2). `Retry-After` siger hvornår.\n\n`too_many_holds`: loftet på aktive reservationer er nået (SPEC 6.5). Det er IKKE en\nthrottling — at vente hjælper ikke. Gør et hold færdigt, eller giv det fri med\n`DELETE /slot_holds/{id}`. `conflict.max_active_holds` siger hvad loftet er, og\n`conflict.scope` om det blev ramt pr. session eller pr. IP.\n\nMulige `code`-værdier: `rate_limited`, `too_many_holds`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["System"],"operationId":"deleteSession","summary":"Log ud — tilbagekaldes med det samme","description":"Sessionen tilbagekaldes i databasen, ikke kun i browseren. Derfor er den serverside: \"log receptionens fælles skærm ud\" skal virke i samme sekund.","responses":{"204":{"description":"Logget ud."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/blueprints":{"get":{"tags":["System"],"operationId":"listBlueprints","summary":"Branchepakker — det man kan starte fra","description":"SPEC §3 lag 6: *\"Versioneret JSON i control plane der seeder lag 1–5 på én gang.\"*\nEfter anvendelse er alt almindelig redigerbar data — pakken er en startopsætning,\nikke en tilstand huset er i bagefter.\n\n**Uden legitimation.** Tilmeldingssiden skal kunne vise hvad man kan vælge imellem,\nfør der findes et hus at udstede en nøgle til, og listen er navnene på de pakker vi\nsælger. En forhandler der bygger sin egen tilmelding spørger det samme sted, så der\naldrig findes to lister over hvad der kan vælges.","security":[],"responses":{"200":{"description":"Pakkerne denne installation kender.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"type":"object","properties":{"object":{"type":"string","enum":["blueprint"]},"key":{"type":"string","example":"restaurant"},"version":{"type":"integer","example":1},"name":{"type":"string","example":"Restaurant"},"description":{"type":"string","nullable":true},"examples":{"type":"array","items":{"type":"string"},"description":"To-tre konkrete ting huset kan booke umiddelbart efter tilmeldingen."},"notes":{"type":"array","items":{"type":"string"},"description":"Forbehold der hører til pakken — fx at kernen ikke rummer sundhedsdata."}}}},"has_more":{"type":"boolean"}}}}}}}}},"/blueprint":{"get":{"tags":["System"],"operationId":"getBlueprint","summary":"Husets egen branchepakke, og en eventuel ny udgave","description":"Ental, i modsætning til `/v1/blueprints` (den offentlige liste over hvad man kan\nvælge FØR man har et hus). Dette er husets: hvilken pakke kom vi fra, hvilken\nversion står vi på, og hvad ville en opdatering betyde.\n\nSPEC §3 lag 6: pakken huskes som key + version, så vi kan tilbyde \"opdatér til v4\"\nsom en diff kunden godkender — aldrig tvinge. `upgrade.changes` rummer KUN\ntilføjelser og standardværdier huset ikke selv har rettet; det huset HAR rettet\nstår i `upgrade.respected` som en kvittering, ikke som et forslag.","responses":{"200":{"description":"Pakken huset kom fra, og diffen til den nyeste udgave.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["blueprint_state"]},"applied":{"type":"object","nullable":true,"properties":{"key":{"type":"string","example":"konference"},"version":{"type":"integer","example":1},"name":{"type":"string","nullable":true},"applied_at":{"type":"integer"},"latest_version":{"type":"integer","example":2}}},"upgrade":{"type":"object","nullable":true,"properties":{"object":{"type":"string","enum":["blueprint_upgrade"]},"key":{"type":"string"},"from_version":{"type":"integer"},"to_version":{"type":"integer"},"available":{"type":"boolean"},"changes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"offering.add:opfoelgning"},"kind":{"type":"string","enum":["add","default"]},"entity":{"type":"string","example":"offering"},"label":{"type":"string"},"detail":{"type":"string"},"before":{"type":"string","nullable":true},"after":{"type":"string","nullable":true}}}},"respected":{"type":"array","items":{"type":"object"}}}}}}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/blueprint/upgrade":{"post":{"tags":["System"],"operationId":"upgradeBlueprint","summary":"Godkend de punkter huset vil have med","description":"Kroppen er en LISTE af punkter fra `GET /v1/blueprint`, aldrig et versionsnummer:\nder findes ingen vej gennem dette API der flytter et hus til en ny udgave uden at\nnogen har set hvad der stod på listen.\n\nEn tom liste er et gyldigt svar. Den betyder \"nej tak til det hele\", og punkterne\nbliver ikke tilbudt igen.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["accept"],"properties":{"accept":{"type":"array","items":{"type":"string"},"example":["offering.add:opfoelgning"]}}}}}},"responses":{"200":{"description":"Hvad der blev skrevet, og hvad der blev valgt fra.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["blueprint_upgrade_result"]},"key":{"type":"string"},"from_version":{"type":"integer"},"to_version":{"type":"integer"},"applied":{"type":"integer"},"accepted":{"type":"array","items":{"type":"string"}},"declined":{"type":"array","items":{"type":"string"}}}}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/signup":{"post":{"tags":["System"],"operationId":"signUp","summary":"Opret en ny organisation — selvbetjent","description":"SPEC §1.2 #1 og §7 fase 2. Opretter en tenant-database, anvender branchepakken,\nskriver huset i control plane med status `trial` og sætter en session-cookie, så\nkalderen er logget ind når svaret kommer tilbage.\n\n**Uden legitimation, fordi der ikke findes nogen at kræve** — huset findes først\nnår kaldet er lykkedes. Til gengæld: loft pr. IP, et honningkrukke-felt (`hjemmeside`,\nsom skal være tomt), og en kontakt der kan lukke endpointet helt (`LES_SIGNUP=closed`,\nsom er standarden i produktion — en selvhostet kasse skal ikke have en åben husfabrik).\n\n**Der spørges ikke om betalingsoplysninger.** SPEC 8 #5: 30 dages fri prøve uden kort.\nAdressen (`/book/<slug>`) udledes af organisationsnavnet og kan rettes bagefter under\nOpsætning › Bookingside.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["organisation","name","email","password"],"properties":{"organisation":{"type":"string","minLength":2,"maxLength":80},"name":{"type":"string","minLength":2,"maxLength":80,"description":"Administratorens navn."},"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":10},"blueprint_key":{"type":"string","description":"Fra `GET /v1/blueprints`. Udelades den, bliver det den TOMME opsætning (`minimal`) — et hus der ikke bad om et konferencecenter skal ikke vågne op med to sale."},"timezone":{"type":"string","example":"Europe/Copenhagen"},"hjemmeside":{"type":"string","description":"Honningkrukke. Skal være tom — et udfyldt felt afvises med 422."}}}}}},"responses":{"201":{"description":"Huset er oprettet. `Set-Cookie` indeholder sessionen.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["signup"]},"tenant_id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string","enum":["trial"]},"blueprint":{"type":"object","properties":{"key":{"type":"string"},"version":{"type":"integer"}}},"trial":{"type":"object","properties":{"days":{"type":"integer","example":30},"days_left":{"type":"integer"},"ends_at":{"type":"string","format":"date-time","nullable":true}}},"admin_url":{"type":"string","example":"/admin"},"booking_url":{"type":"string","example":"/book/frisoer-bo"}}}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited`: for mange kald pr. nøgle (SPEC 4.2). `Retry-After` siger hvornår.\n\n`too_many_holds`: loftet på aktive reservationer er nået (SPEC 6.5). Det er IKKE en\nthrottling — at vente hjælper ikke. Gør et hold færdigt, eller giv det fri med\n`DELETE /slot_holds/{id}`. `conflict.max_active_holds` siger hvad loftet er, og\n`conflict.scope` om det blev ramt pr. session eller pr. IP.\n\nMulige `code`-værdier: `rate_limited`, `too_many_holds`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/onboarding":{"get":{"tags":["System"],"operationId":"getOnboarding","summary":"Kom-i-gang-listen — afledt af husets egne data","description":"De fire-fem ting der gør et nyt hus kørende, hver med en URL til stedet hvor man\ngør det. **Intet krydses af i hånden:** hvert punkts `done` er en forespørgsel mod\nhusets katalog og bookinger, så listen kan ikke sige \"færdig\" om et hus uden ét\nlokale. `GET` skriver ikke noget.\n\n`visible` er falsk når listen enten er fuldført eller skjult — fladen behøver ikke\nselv finde ud af hvornår den skal forsvinde.","responses":{"200":{"description":"Punkterne og fremdriften.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["onboarding"]},"visible":{"type":"boolean"},"completed":{"type":"boolean"},"done":{"type":"integer"},"total":{"type":"integer"},"dismissed":{"type":"boolean"},"steps":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","example":"resource"},"title":{"type":"string"},"body":{"type":"string"},"done":{"type":"boolean"},"href":{"type":"string"},"action":{"type":"string"}}}}}}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["System"],"operationId":"updateOnboarding","summary":"Skjul (eller genfind) kom-i-gang-listen","description":"Gemmes i husets egne indstillinger og ikke i browseren: en der har skjult listen på kontoret skal ikke møde den igen på tablet'en i køkkenet.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["dismissed"],"properties":{"dismissed":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Listens nye tilstand."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/offerings":{"get":{"tags":["Katalog"],"operationId":"listOfferings","summary":"Ydelser","description":"SPEC 0 #6: ÉN tabel med `kind IN (\"service\",\"addon\",\"package\",\"fee\")`. Der findes ingen\n`/services` og ingen `/add_ons`.\n\nSPEC 2.6 #6: intet slettes. `?archived=true` inkluderer arkiverede, `?archived=only` viser kun dem.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"starting_after","in":"query","schema":{"type":"string"},"description":"Cursor: id fra forrige sides `next_cursor` (SPEC 4.2)."},{"name":"kind","in":"query","schema":{"type":"string","enum":["service","addon","package","fee"]}},{"name":"location_id","in":"query","schema":{"type":"string"}},{"name":"slug","in":"query","schema":{"type":"string"}},{"name":"archived","in":"query","schema":{"type":"string","enum":["true","only"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferingList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Katalog"],"operationId":"createOffering","summary":"Opret ydelse","description":"SPEC 5.3 #4: seks felter giver en fungerende ydelse. Resten har defaults.\n\nYdelsen får automatisk sit ene segment (SPEC 0 #13), og `requirements` skrives på det.\nEn ydelse UDEN krav afvises: uden et krav er der ikke noget der kan være optaget, og så\ner alt altid ledigt.\n\nSPEC 6.15: skrivningen og dens audit-række deler én transaktion. \"Hvem satte kapaciteten ned\ntil 60\" bliver spurgt om i uge to.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferingInput"}}}},"responses":{"200":{"description":"Fandtes allerede via `external_ref` (SPEC 4.8).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferingDetail"}}}},"201":{"description":"Oprettet.","headers":{"ETag":{"description":"Objektets `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferingDetail"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/offerings/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Katalog"],"operationId":"getOffering","summary":"Ydelse — med krav","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Objektets `updated_at` — tokenet PATCH kræver.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferingDetail"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Katalog"],"operationId":"updateOffering","summary":"Ret ydelse — kræver If-Match","description":"**Ændringer rammer aldrig en booking der findes** (aksiom 4). Pris, moms, varighed, buffer,\nvilkår og opstillingskapacitet er kopieret ned på linjen ved oprettelsen — SPEC 6.7 siger det\ndirekte om buffere. Det er dét der gør endpointet trygt at bruge en tirsdag eftermiddag med\ntre hundrede bookinger i kalenderen.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferingInput"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferingDetail"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Katalog"],"operationId":"archiveOffering","summary":"Arkivér ydelse (DELETE = arkivering)","description":"SPEC 2.6 #6 / 6.15: **intet slettes.** `DELETE` sætter `archived_at`, og objektet kommer\ntilbage i svaret med `archived: true`.\n\nArkivering afvises så længe der stadig ligger FREMTIDIGE blokerende allokeringer på objektet —\nog afvisningen bærer listen over dem i `alternatives`. SPEC 6.15 formulerer det selv\n(\"afvises med listen over dem\"), og SPEC 4.7 kræver at en 409 altid peger videre.\n\nAflyste bookinger tæller ikke med: deres allokeringsrække bliver liggende for altid på\n`block_level = 0` (SPEC 2.3), så \"hvorfor var lokalet optaget den dag\" kan besvares et år\nsenere — uden at spærre for at arkivere lokalet.","responses":{"200":{"description":"Arkiveret (eller allerede arkiveret).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferingDetail"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/resources":{"get":{"tags":["Katalog"],"operationId":"listResources","summary":"Ressourcer","description":"SPEC 4.1 #2: ressource + krav er modellen, også i API'et. Et lokale, en tjenerpulje og\nkøkkenets kuvertkapacitet er alle rækker her — ingen `/tables`, ingen `/staff`.\n\nSPEC 2.6 #6: intet slettes. `?archived=true` inkluderer arkiverede, `?archived=only` viser kun dem.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"starting_after","in":"query","schema":{"type":"string"},"description":"Cursor: id fra forrige sides `next_cursor` (SPEC 4.2)."},{"name":"location_id","in":"query","schema":{"type":"string"}},{"name":"resource_type_id","in":"query","schema":{"type":"string"}},{"name":"archived","in":"query","schema":{"type":"string","enum":["true","only"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Katalog"],"operationId":"createResource","summary":"Opret ressource","description":"`component_resource_ids` gør den sammensat (SPEC 2.4): den afledte `resource_conflict`-lukning\ngenberegnes i samme transaktion, så en booking på Sal A fjerner Sal A+B fra kalenderen — og\nomvendt — uden en kodesti af sin egen.\n\nSPEC 6.15: skrivningen og dens audit-række deler én transaktion. \"Hvem satte kapaciteten ned\ntil 60\" bliver spurgt om i uge to.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceInput"}}}},"responses":{"200":{"description":"Fandtes allerede via `external_ref` (SPEC 4.8).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Resource"}}}},"201":{"description":"Oprettet.","headers":{"ETag":{"description":"Objektets `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Resource"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/resources/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Katalog"],"operationId":"getResource","summary":"Ressource","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Objektets `updated_at` — tokenet PATCH kræver.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Resource"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Katalog"],"operationId":"updateResource","summary":"Ret ressource — kræver If-Match","description":"**At sætte kapaciteten NED blokeres ikke** (SPEC 6.6 [A]): \"bloker ikke ændringen; vis hvilke\ndatoer der nu er overbooket og lad admin beslutte.\" Huset ved bedst hvad der står i lokalet.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceInput"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Resource"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Katalog"],"operationId":"archiveResource","summary":"Arkivér ressource (DELETE = arkivering)","description":"SPEC 2.6 #6 / 6.15: **intet slettes.** `DELETE` sætter `archived_at`, og objektet kommer\ntilbage i svaret med `archived: true`.\n\nArkivering afvises så længe der stadig ligger FREMTIDIGE blokerende allokeringer på objektet —\nog afvisningen bærer listen over dem i `alternatives`. SPEC 6.15 formulerer det selv\n(\"afvises med listen over dem\"), og SPEC 4.7 kræver at en 409 altid peger videre.\n\nAflyste bookinger tæller ikke med: deres allokeringsrække bliver liggende for altid på\n`block_level = 0` (SPEC 2.3), så \"hvorfor var lokalet optaget den dag\" kan besvares et år\nsenere — uden at spærre for at arkivere lokalet.","responses":{"200":{"description":"Arkiveret (eller allerede arkiveret).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Resource"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/resources/{id}/setups":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Katalog"],"operationId":"listSetups","summary":"Opstillinger","description":"SPEC 0 #2: `max_persons` validerer `pax` og allokeres **aldrig**. Sal A er 300 i biograf,\n180 i klasse og 60 i U-bord — ét lokale, tre svar, og at blande dem med `capacity` er den\nklassiske grund til at \"Sal A: 300\" pludselig betyder tre ting.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetupList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Katalog"],"operationId":"createSetup","summary":"Opret opstilling","description":"`changeover_min` er opstillingens andel af SPEC 6.7's\n`max(resource.turnaround_min, setup.changeover_min)`, lagt på begge sider af bookingen.\n\nSPEC 6.15: skrivningen og dens audit-række deler én transaktion. \"Hvem satte kapaciteten ned\ntil 60\" bliver spurgt om i uge to.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetupInput"}}}},"responses":{"200":{"description":"Fandtes allerede via `external_ref` (SPEC 4.8).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Setup"}}}},"201":{"description":"Oprettet.","headers":{"ETag":{"description":"Objektets `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Setup"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/resources/{id}/setups/{setup_id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"setup_id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Katalog"],"operationId":"getSetup","summary":"Opstilling","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Setup"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Katalog"],"operationId":"updateSetup","summary":"Ret opstilling — kræver If-Match","description":"At sætte `max_persons` ned under hvad en fremtidig booking allerede bærer, blokeres ikke\n(SPEC 6.6 [A]): tallet på linjen er et snapshot fra oprettelsen (SPEC 2.6 #5), så intet\nallerede solgt ændrer sig under nogen.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetupInput"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Setup"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Katalog"],"operationId":"archiveSetup","summary":"Arkivér opstilling (DELETE = arkivering)","description":"SPEC 2.6 #6 / 6.15: **intet slettes.** `DELETE` sætter `archived_at`, og objektet kommer\ntilbage i svaret med `archived: true`.\n\nArkivering afvises så længe der stadig ligger FREMTIDIGE blokerende allokeringer på objektet —\nog afvisningen bærer listen over dem i `alternatives`. SPEC 6.15 formulerer det selv\n(\"afvises med listen over dem\"), og SPEC 4.7 kræver at en 409 altid peger videre.\n\nAflyste bookinger tæller ikke med: deres allokeringsrække bliver liggende for altid på\n`block_level = 0` (SPEC 2.3), så \"hvorfor var lokalet optaget den dag\" kan besvares et år\nsenere — uden at spærre for at arkivere lokalet.","responses":{"200":{"description":"Arkiveret (eller allerede arkiveret).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Setup"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/resources/{id}/schedules":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Katalog"],"operationId":"getResourceSchedule","summary":"Hvilken åbningstid gælder for ressourcen — og er den arvet?","description":"SPEC 6.9's lagdeling er usynlig for et UI, medmindre API'et siger hvilket lag svaret kom\nfra. Derfor begge dele: skemaet der GÆLDER, og om det er ressourcens eget eller husets.\n\"Køkkenet lukker 21, huset 23\" er SPEC'ens eget eksempel, og den der kigger på køkkenet\nskal kunne se hvilket af de to tal hun redigerer.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceSchedule"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Katalog"],"operationId":"detachResourceSchedule","summary":"Giv ressourcen sin egen åbningstid","description":"Opretter et skema og hægter det på ressourcen — **seedet med de timer den arver lige nu**.\nSPEC 5.5 #3: \"Systemet foreslår, mennesket bekræfter.\" Et tomt ugeskema er værre end en tom\ndropdown, for SPEC 6.9's fallback for \"ingen regler overhovedet\" er *altid åbent*: at rive\nkøkkenet løs fra huset ville ellers åbne det døgnet rundt med ét klik.\n\nHar ressourcen allerede sit eget skema, redigeres DET — der oprettes aldrig et nummer to.\nTilbage til arven er `PATCH /v1/resources/{id} {\"schedule_id\": null}`: samme felt fra den\nanden side, ikke et fjerde verbum.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleInput"}}}},"responses":{"201":{"description":"Ressourcen har nu sin egen åbningstid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Schedule"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/schedules":{"get":{"tags":["Katalog"],"operationId":"listSchedules","summary":"Åbningstider","description":"SPEC 6.9. Et skema er en navngiven uge, som en lokation, en ressource *eller* en ydelse kan\npege på. At holde det som et selvstændigt objekt frem for kolonner på hver af de tre er\npræcis dét der får lagdelingen til at virke.\n\n`used_by` siger hvem der peger på skemaet, så et UI kan skrive \"Sal A følger husets\nåbningstid\" uden et kald pr. ressource.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Katalog"],"operationId":"createSchedule","summary":"Opret åbningstid","description":"`open_min`/`close_min` er minutter siden LOKAL midnat, og `close_min` må overstige 1440\n(baren der lukker 02:00 = 1560).\n\nSPEC 6.15: skrivningen og dens audit-række deler én transaktion. \"Hvem satte kapaciteten ned\ntil 60\" bliver spurgt om i uge to.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleInput"}}}},"responses":{"200":{"description":"Fandtes allerede via `external_ref` (SPEC 4.8).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Schedule"}}}},"201":{"description":"Oprettet.","headers":{"ETag":{"description":"Objektets `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Schedule"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/schedules/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Katalog"],"operationId":"getSchedule","summary":"Åbningstid med ugeregler og undtagelser","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Schedule"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Katalog"],"operationId":"updateSchedule","summary":"Ret åbningstid — kræver If-Match","description":"`rules` ERSTATTER hele ugeskemaet. Udelad feltet, og kun navnet ændres.\n\nDer findes ingen DELETE: et skema som en lokation eller ressource stadig peger på ville tage\nåbningstiden med sig, og SPEC 6.9's fallback for \"intet skema\" er *altid åbent*.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleInput"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Schedule"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/schedules/{id}/exceptions":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Katalog"],"operationId":"listScheduleExceptions","summary":"Lukkedage og særlige tider","parameters":[{"name":"from","in":"query","schema":{"type":"string"},"examples":{"dato":{"value":"2026-08-01"}}},{"name":"to","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleExceptionList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Katalog"],"operationId":"createScheduleException","summary":"Luk en periode, eller giv den særlige tider","description":"SPEC 6.9 lag 3, og SPEC 4.4's `/unavailabilities` under det navn datamodellen faktisk bruger.\n\n**En undtagelse oven på eksisterende bookinger er [S], ikke [H]:** \"bloker ikke; vis listen\nover berørte bookinger og lad admin flytte/aflys.\" Undtagelsen oprettes, og de berørte\nbookinger kommer med i svaret som `affected_bookings`. At afvise ville betyde at et hus ikke\nkan skrive ned at det holder lukket den 5., før det har taget stilling til den 5. — omvendt\naf hvordan telefonsamtalen forløber.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["date_from","kind"],"properties":{"date_from":{"type":"string","examples":["2026-08-03"]},"date_to":{"type":"string","description":"Udelades: samme dag."},"kind":{"type":"string","enum":["closed","special"]},"open_min":{"type":["integer","null"],"description":"Kun ved `special`, og så påkrævet."},"close_min":{"type":["integer","null"]},"note":{"type":["string","null"]}}}}}},"responses":{"201":{"description":"Oprettet. `affected_bookings` er dem der ligger i perioden.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ScheduleException"},{"type":"object","properties":{"affected_bookings":{"type":"array","items":{"type":"object","properties":{"booking_id":{"type":"string"},"reference":{"type":"string"},"status":{"type":"string"},"starts_at":{"type":"integer"},"resource_names":{"type":["string","null"],"description":"Kommasepareret. Én booking kan holde både salen, køkkenet og to tjenere — listen er én række pr. BOOKING, ikke pr. allokering."}}}}}}]}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/schedules/{id}/exceptions/{exception_id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"exception_id","in":"path","required":true,"schema":{"type":"string"}}],"patch":{"tags":["Katalog"],"operationId":"updateScheduleException","summary":"Ret undtagelse — kræver If-Match","description":"Felter der udelades beholdes fra rækken, og hele resultatet valideres samlet — `kind: \"special\"`\nuden tider er en anden fejl end \"du ændrede kun noten\".\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleException"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleException"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Katalog"],"operationId":"deleteScheduleException","summary":"Fjern undtagelsen","description":"Det ene sted i kataloget hvor DELETE virkelig fjerner en række. En `schedule_exception` er\nikke noget der SKETE — det er en regel der var i kraft, og SPEC 2.6 #6's `archived_at`\nfindes for at historik kan besvares (\"hvorfor var lokalet optaget den dag\"). En ophævet\nundtagelse blokerer intet og forklarer intet; en gravsten ville få `windowsForDate` til at\nlæse en regel huset har trukket tilbage. Audit-loggen bærer at den fandtes.","responses":{"204":{"description":"Fjernet."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/holidays":{"get":{"tags":["Katalog"],"operationId":"listHolidays","summary":"Helligdage","description":"SPEC 6.9: \"**Danske helligdage: hardcode dem aldrig.**\" Store bededag blev afskaffet med\nvirkning fra 2024, så enhver hardkodet liste i landet er allerede forkert.\n\nTo ting tabellen IKKE er: den lukker ikke noget af sig selv (et kongrescenter er ofte åbent\n2. juledag — lukning er en `schedule_exception`), og den er ikke kun til tillæg: den er dét\nder giver \"5 hverdage før\" mening (SPEC 6.13's garantifrist).","parameters":[{"name":"year","in":"query","schema":{"type":"integer"}},{"name":"from","in":"query","schema":{"type":"string"}},{"name":"to","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HolidayList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Katalog"],"operationId":"createHoliday","summary":"Tilføj en dag — eller generér et helt år","description":"`{\"generate_year\": 2028}` fylder året ud (påske via Meeus, resten faste datoer) og rører\n**ikke** rækker en admin allerede har rettet: unikhedsindekset på datoen gør genereringen\ntil `INSERT OR IGNORE`. Tabellen er husets, så snart den findes.\n\n`{\"date\": \"...\", \"name\": \"...\"}` tilføjer én dag — jeres egen lukkedag, en lokal mærkedag.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["date","name"],"properties":{"date":{"type":"string","examples":["2026-12-26"]},"name":{"type":"string"},"is_national":{"type":"boolean","default":true}}},{"type":"object","required":["generate_year"],"properties":{"generate_year":{"type":"integer","examples":[2028]}}}]}}}},"responses":{"201":{"description":"Oprettet. Ved `generate_year` svares med hele året plus `added`.","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/Holiday"},{"$ref":"#/components/schemas/HolidayList"}]}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/holidays/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"patch":{"tags":["Katalog"],"operationId":"updateHoliday","summary":"Ret helligdag — kræver If-Match","description":"SPEC 6.9: \"og lad admin redigere\". Navn, dato og om dagen tæller som helligdag.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Holiday"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Holiday"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Katalog"],"operationId":"deleteHoliday","summary":"Fjern helligdagen","description":"Fjerner rækken. Som `schedule_exception` er en helligdag en regel i kraft frem for noget der skete, og en generator der lægger året ind igen må ikke blokeres af en gravsten.","responses":{"204":{"description":"Fjernet."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/resource_types":{"get":{"tags":["Katalog"],"operationId":"listResourceTypes","summary":"Ressourcetyper","description":"SPEC 3 lag 3: `label_singular`/`label_plural` bærer hele fase 1's terminologilag.\n`lokale` / `bord` / `stol` er en række, aldrig en kodeforgrening.\n\nSPEC 2.6 #6: intet slettes. `?archived=true` inkluderer arkiverede, `?archived=only` viser kun dem.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"starting_after","in":"query","schema":{"type":"string"},"description":"Cursor: id fra forrige sides `next_cursor` (SPEC 4.2)."},{"name":"key","in":"query","schema":{"type":"string"}},{"name":"archived","in":"query","schema":{"type":"string","enum":["true","only"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceTypeList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Katalog"],"operationId":"createResourceType","summary":"Opret ressourcetype","description":"Uden dette endpoint ville en restaurant der vil kalde et lokale for et bord kræve en\nkodeændring — og det er præcis den fejltilstand SPEC 1.3 siger aldrig må opstå.\n\nSPEC 6.15: skrivningen og dens audit-række deler én transaktion. \"Hvem satte kapaciteten ned\ntil 60\" bliver spurgt om i uge to.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceTypeInput"}}}},"responses":{"200":{"description":"Fandtes allerede via `external_ref` (SPEC 4.8).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceType"}}}},"201":{"description":"Oprettet.","headers":{"ETag":{"description":"Objektets `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceType"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/resource_types/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Katalog"],"operationId":"getResourceType","summary":"Ressourcetype","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Objektets `updated_at` — tokenet PATCH kræver.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceType"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Katalog"],"operationId":"updateResourceType","summary":"Ret ressourcetype — kræver If-Match","description":"\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceTypeInput"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceType"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Katalog"],"operationId":"archiveResourceType","summary":"Arkivér ressourcetype (DELETE = arkivering)","description":"SPEC 2.6 #6 / 6.15: **intet slettes.** `DELETE` sætter `archived_at`, og objektet kommer\ntilbage i svaret med `archived: true`.\n\nArkivering afvises så længe der stadig ligger FREMTIDIGE blokerende allokeringer på objektet —\nog afvisningen bærer listen over dem i `alternatives`. SPEC 6.15 formulerer det selv\n(\"afvises med listen over dem\"), og SPEC 4.7 kræver at en 409 altid peger videre.\n\nAflyste bookinger tæller ikke med: deres allokeringsrække bliver liggende for altid på\n`block_level = 0` (SPEC 2.3), så \"hvorfor var lokalet optaget den dag\" kan besvares et år\nsenere — uden at spærre for at arkivere lokalet.","responses":{"200":{"description":"Arkiveret (eller allerede arkiveret).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceType"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/locations":{"get":{"tags":["Katalog"],"operationId":"listLocations","summary":"Lokationer","description":"`timezone` er IANA-strengen alle tidsstempler i API'et udtrykkes i (SPEC 6.10).\n\nSPEC 2.6 #6: intet slettes. `?archived=true` inkluderer arkiverede, `?archived=only` viser kun dem.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"starting_after","in":"query","schema":{"type":"string"},"description":"Cursor: id fra forrige sides `next_cursor` (SPEC 4.2)."},{"name":"archived","in":"query","schema":{"type":"string","enum":["true","only"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LocationList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Katalog"],"operationId":"createLocation","summary":"Opret lokation","description":"`timezone` valideres mod platformens tidszonedatabase. Aldrig et fast offset (SPEC 6.10).\n\nSPEC 6.15: skrivningen og dens audit-række deler én transaktion. \"Hvem satte kapaciteten ned\ntil 60\" bliver spurgt om i uge to.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LocationInput"}}}},"responses":{"200":{"description":"Fandtes allerede via `external_ref` (SPEC 4.8).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"}}}},"201":{"description":"Oprettet.","headers":{"ETag":{"description":"Objektets `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/locations/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Katalog"],"operationId":"getLocation","summary":"Lokation","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Objektets `updated_at` — tokenet PATCH kræver.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Katalog"],"operationId":"updateLocation","summary":"Ret lokation — kræver If-Match","description":"`schedule_id` er SPEC 6.9's lag 1 — husets uge, den alle ressourcer falder tilbage på.\n\n`timezone` kan ikke ændres når der findes bookinger: hver eneste af dem ville flytte sig på\nuret. Svaret er 409 med antallet.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LocationInput"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/customers":{"get":{"tags":["Katalog"],"operationId":"listCustomers","summary":"Kunder — søg på navn, firma, telefon, mail, CVR og EAN","description":"SPEC 5.3 #3 åbner bookingpanelet med \"kundesøgning først, på navn/tlf/mail/EAN\", og\nSPEC 5.2 giver kunderne deres egen skærm. `?q=` matcher det hele på én gang.\n\nTelefonnumre sammenlignes på cifre alene, så \"20 12 34 56\" finder \"+4520123456\".\nMed `?q=` sorteres der på navn (et opslag), uden `?q=` på id (en cursor-side).","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"starting_after","in":"query","schema":{"type":"string"},"description":"Cursor: id fra forrige sides `next_cursor` (SPEC 4.2)."},{"name":"q","in":"query","schema":{"type":"string"},"description":"Fritekst på tværs af felterne ovenfor."},{"name":"email","in":"query","schema":{"type":"string"}},{"name":"ean","in":"query","schema":{"type":"string"}},{"name":"cvr","in":"query","schema":{"type":"string"}},{"name":"kind","in":"query","schema":{"type":"string","enum":["person","company"]}},{"name":"archived","in":"query","schema":{"type":"string","enum":["true","only"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Katalog"],"operationId":"createCustomer","summary":"Opret kunde","description":"SPEC 6.14: `cvr` valideres som 8 cifre med modulus-11 og `ean` som 13 cifre med\nEAN-13-kontrolciffer. En tastefejl i et EAN-nummer opdages ellers seks uger senere,\nnår kommunens faktura afvises.\n\n`external_ref` er unik pr. organisation for altid (SPEC 4.8) — en CRM-import der\nkøres to gange får den samme kunde tilbage, ikke kunde nummer to.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerInput"}}}},"responses":{"200":{"description":"Fandtes allerede via `external_ref`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"201":{"description":"Oprettet.","headers":{"ETag":{"description":"Kundens `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/customers/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Katalog"],"operationId":"getCustomer","summary":"Kunde","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Kundens `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Katalog"],"operationId":"updateCustomer","summary":"Ret kunde — kræver If-Match","description":"SPEC 4.4 kræver optimistisk låsning på alt mutérbart. `customer` er den ene\nkernetabel uden en `version`-kolonne (SPEC 2.5), så dens `updated_at` er tokenet:\n`ETag: \"<updated_at>\"`, og en PATCH med et forældet token er `412 version_conflict`\nmed det aktuelle objekt vedhæftet — samme form som en booking svarer med.\n\nDer findes ingen DELETE. Kunder **anonymiseres** når der findes fakturalinjer\n(SPEC 6.15, bogføringsloven), og det er sin egen handling med sine egne følger.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerInput"}}}},"responses":{"200":{"description":"Rettet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cancel_reasons":{"get":{"tags":["Katalog"],"operationId":"listCancelReasons","summary":"Aflysningsårsager","description":"SPEC 6.11: *\"aflysningsårsager som redigerbar liste fra dag ét — hele grundlaget for\nrapporten \"hvorfor mister vi bookinger\".\"*\n\n`POST /bookings/{id}/cancel` og `/decline` tager `cancel_reason_id` fra den her liste.\nFritekst alene er en kolonne ingen kan tælle.","responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Katalog"],"operationId":"createCancelReason","summary":"Opret en aflysningsårsag","description":"`is_no_show` markerer en årsag der BETYDER \"de kom aldrig\" — SPEC 6.8 [H] holder de to\nadskilt i rapporterne, også når huset har skrevet sit eget ord for det.\n\nNye årsager lander nederst; husets egen rækkefølge bliver aldrig blandet om.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label"],"properties":{"label":{"type":"string","maxLength":120},"is_no_show":{"type":"boolean","default":false},"counts_as_lost_revenue":{"type":"boolean","default":true},"sort_order":{"type":"integer"}}}}}},"responses":{"201":{"description":"Oprettet."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cancel_policies":{"get":{"tags":["Katalog"],"operationId":"listCancelPolicies","summary":"Afbestillingsvilkår","description":"SPEC 6.11: den ordnede trappe, der **snapshottes** på bookingen ved bekræftelsen.\nÆndrer huset vilkårene i marts, gælder de gamle for en booking bekræftet i februar —\ndet er både juridisk nødvendigt og forudsætningen for at kunne forklare et gebyr.\n\nSPEC 8 #3: vilkårene skal kunne ændres i opsætningen **uden kode**. Derfor findes\nendpointet, og derfor er `version` serverens: en trappe der kunne skrives om under et\nversionsnummer der stod stille, ville gøre snapshottet ucitérbart.","responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Katalog"],"operationId":"createCancelPolicy","summary":"Opret en afbestillingstrappe","description":"Trappen skal kunne LÆSES som en trappe: flest dage først, og gebyret må ikke falde jo\ntættere på man kommer. Begge dele afvises med 422 der navngiver de to trin det handler\nom — en trappe der går den forkerte vej er ikke en tastefejl i rækkefølgen, det er\nnogen der ikke har besluttet hvad vilkårene er.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","tiers"],"properties":{"name":{"type":"string","maxLength":120},"tiers":{"type":"array","minItems":1,"items":{"type":"object","required":["days_before","fee_pct"],"properties":{"days_before":{"type":"integer","minimum":0},"fee_pct":{"type":"integer","minimum":0,"maximum":100}}}},"customer_may_cancel_until_hours":{"type":["integer","null"]},"customer_may_reschedule_until_hours":{"type":["integer","null"]},"free_reschedule_count":{"type":"integer","minimum":0}}}}}},"responses":{"201":{"description":"Oprettet."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/participant_categories":{"get":{"tags":["Katalog"],"operationId":"listParticipantCategories","summary":"Deltagerkategorier","description":"SPEC 2.6 #2: deltagere tælles pr. kategori fra dag ét (\"Voksen 40, Barn 5\"), fordi\ndet ikke kan eftermonteres uden at omskrive alle prisregler og alle kapacitetstjek.\n\n`counts_toward_capacity` er ikke pynt: en kategori der ikke optager en plads må ikke\nlægges til det tal lokalet måles mod.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"starting_after","in":"query","schema":{"type":"string"},"description":"Cursor: id fra forrige sides `next_cursor` (SPEC 4.2)."},{"name":"archived","in":"query","schema":{"type":"string","enum":["true"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantCategoryList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/field_defs":{"get":{"tags":["Katalog"],"operationId":"listFieldDefs","summary":"Kundens egne felter — konfigurationsmodellens lag 2","description":"SPEC 3 lag 2. Et spørgsmål som \"Hvilket AV-udstyr skal I bruge?\" er en **række**,\nikke en kodeændring; svaret ligger i `booking.custom`.\n\n`visibility` afgør hvem der ser feltet. `?visibility=public` giver de felter gæsten\nskal svare på — filtreres der i klienten i stedet, vises et internt spørgsmål til\nen kunde den dag nogen glemmer det.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"starting_after","in":"query","schema":{"type":"string"},"description":"Cursor: id fra forrige sides `next_cursor` (SPEC 4.2)."},{"name":"entity","in":"query","schema":{"type":"string","enum":["booking","booking_line","customer","resource","offering"]}},{"name":"visibility","in":"query","schema":{"type":"string","enum":["public","admin"]},"description":"`both` følger altid med."},{"name":"offering_id","in":"query","schema":{"type":"string"},"description":"Globale felter plus dem der er bundet til denne ydelse."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldDefList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/events":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Booking"],"operationId":"listBookingEvents","summary":"Bookingens historik — hvem gjorde hvad, hvornår","description":"SPEC 2.5: `booking_event` er append-only, og SPEC 6.15 lægger en række i den ved\nhver eneste skrivning. \"Hvem flyttede den booking\" bliver spurgt om i uge to.\n\nBevidst ikke `?expand=events`: SPEC 4.2 fastlægger fase 1's expand-sæt til netop\n`customer,offering,resources`, og en historik er ubegrænset — en booking der er\nflyttet fyrre gange må ikke gøre hver eneste `GET /bookings`-side fyrre gange tungere.\n\nNyeste først. `ending_before` pager bagud.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"ending_before","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingEventList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/documents":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"createBookingDocument","summary":"Generér køreplanen (BEO) eller tilbuddet i en ny version","description":"SPEC 6.13 — *\"fase 1's stærkeste enkeltfunktion\"*. Køreplanen bygges af bookingens\negne rækker: tider pr. linje, lokale og opstilling, kuverttal pr. kategori, forplejning\nmed antal, allergier, noter, kontakt og garantital — plus hvert `field_def` der er\nmarkeret `in_beo` (SPEC 3 lag 2).\n\n**Versionen bumper kun når arket faktisk er anderledes.** Svaret er `201` med en ny\nversion når noget er ændret, og `200` med den eksisterende når intet er. Sammenligningen\nsker på indholdet, ikke på `booking.version`: en version der bumper uden at røre arket\nmå ikke råbe ulv, og en frist et job sætter uden at bumpe versionen må ikke gemme sig.\n\nFra og med v2 bærer svaret en `diff` mod forrige version, felt for felt, i husets egne\nord: *\"Ændringer siden v3: kuverter 48→52, menu tilføjet Vegetar ×4\"*.\n\n**`{\"type\":\"quote\"}` giver tilbuddet i stedet** (SPEC 6.13's ene spor,\nBACKLOG #222): linjer med priser, momsopstilling pr. sats, depositum, et uddrag af\nafbestillingsvilkårene og en gyldighedsfrist. Linjerne er bookingens egne\n`invoice_line` — tilbuddet beregner ikke en pris, det samler de rækker fakturaen\nsenere stempler, så et tilbud og den faktura der følger af det, ikke kan blive uenige\nom et beløb.\n\nHusets skabelon (SPEC §7 fase 2, `settings.documents.templates`) fryses NED i\nversionen. Retter huset sin indledning eller sin betalingsinstruks i marts, siger\nfebruars tilbud stadig februars ord — samme regel som `policy_snapshot` (SPEC 6.11).\n\nEvents (SPEC 4.9): `document.beo.published` / `document.quote.published` på v1,\n`…revised` på alle senere — sidstnævnte med hele diffen i payloaden, for det er den\nkøkken, opsætning og salg abonnerer på.\n\nKræver modulet `documents` (SPEC 3 lag 5). Er det slået fra, svarer endpointet 404 med\nen sætning der siger hvor det tændes.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["beo","quote"],"default":"beo","description":"`beo` er husets eget ark til køkkenet; `quote` er tilbuddet til kunden (BACKLOG #222). Samme versionsmekanik, samme diff, samme udsendelse."}}}}}},"responses":{"200":{"description":"Uændret — den eksisterende version returneres.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Document"}}}},"201":{"description":"Ny version oprettet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Document"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Booking"],"operationId":"listBookingDocuments","summary":"Dokumentets versioner, nyeste først","parameters":[{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["beo","quote"],"default":"beo"},"description":"Hvilket dokument. Listen er ÉN types versioner: en booking har både en køreplan v3 og et tilbud v2, og de er to nummerrækker."}],"description":"Hver version bærer sit eget frosne indhold og den diff den blev født med — aldrig en\ndiff der genberegnes mod nutiden, for et køkkendokument må ikke ændre sig efter det er\ntrykt.\n\n`preview` er hvad en ny version VILLE indeholde lige nu, så `/admin/beo/[id]` og\n`/admin/tilbud/[id]` kan vise dokumentet før det får et nummer. `changed_since_sent` er\nSPEC 6.13's markering: bookingen har flyttet sig siden den nyeste **udsendte** version —\nfor et tilbud betyder det at kunden sidder med et andet beløb end det der gælder nu.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/documents/{document_id}/send":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Booking"],"operationId":"sendBookingDocument","summary":"Send dokumentet — køreplanen til køkkenet, tilbuddet til kunden","description":"SPEC 5.3: *\"Autosave på felter, eksplicit knap på kommunikation … systemet sender aldrig\nen mail bag ryggen på nogen.\"* At generere en version er billigt og usynligt uden for\nhuset; at sende den er ikke, og derfor er det et selvstændigt verbum — samme form som\n`POST /v1/invoices/{id}/send` (SPEC 4.4).\n\nTypen udledes af dokumentets id, ikke af kroppen: kalderen har allerede sagt hvad der\nskal af sted, og et `type` der kunne modsige id'et ville være to sandheder om det\nsamme — hvoraf den ene før eller siden sender et tilbud til køkkenet.\n\nModtageren er `to`, ellers **husets `documents.kitchen_email`** for en køreplan og\n**bookingens egen kontaktadresse** for et tilbud. De to standarder er ikke ombyttelige.\nMailen er ren tekst med ændringerne øverst.\n\nSPEC 6.15's dedup gælder: samme version til samme adresse sendes én gang, uanset hvor\nmange gange knappen trykkes. `sent_at` sættes kun når adapteren tog imod den — det er\ndet der gør \"ændret siden udsendelse\" sand.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","description":"Overstyrer husets standardadresse."}}}}}},"responses":{"200":{"description":"Sendt (eller allerede sendt — se `message`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Document"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/payments":{"get":{"tags":["Penge"],"operationId":"listPayments","summary":"Registrerede betalinger","description":"SPEC 6.14: *\"Bookingens tilstandsmaskine og betalingens er adskilte. Ingen `paid`\nBOOLEAN.\"* Rækkerne her er den ene halvdel af `amount_due` — den anden er\n`invoice_line`.\n\n`?booking_id=` afgrænser til én booking. Kræver modulet `payment`.","parameters":[{"name":"booking_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Penge"],"operationId":"createPayment","summary":"Registrér en betaling","description":"SPEC 6.14: *\"I fase 1 registrerer personalet manuelle betalinger (bankoverførsel\nmodtaget, EAN-faktura sendt). Når MobilePay kommer, er det en ny `provider`-adapter der\nskriver de samme rækker. Ingen ombygning, og der findes historik fra dag ét.\"*\n\n**`amount_minor` er altid positivt.** Fortegnet udledes af `kind`: en `refund` gemmes\nnegativt, så `SUM(amount_minor)` er svaret på \"hvor meget har vi fået\" uden en\nfortegnsregel man skal huske. Sender man en refusion uden minus, går det ikke galt.\n\n`method` skal være slået til under Opsætning › Betaling. Enum-listen bærer alle metoder\nfra dag ét (SPEC 6.14 + ejerbeslutning 4), men en metode huset ikke har, afvises med\n422 og listen over dem det HAR — en drop-down der tilbyder MobilePay på et hus uden\nMobilePay er et løfte produktet ikke kan holde.\n\n`occurred_at` er RFC 3339 med offset (SPEC 6.10 afviser naive tidsstempler). Udelades\ndet, bruges nu — men \"pengene kom ind i tirsdags\" er hele grunden til at feltet findes.\n\nSvaret bærer bookingens nye stilling (`amount_due_minor`, `payment_status`, `at_risk`),\nså den der lige har talt med kunden kan se om samtalen er slut.\n\nDer findes hverken `PATCH` eller `DELETE`. En betaling der viste sig forkert rettes med\nen `adjustment` eller en `refund` — historik laves ikke om (SPEC 6.15).\n\nEvents: `payment.succeeded` · `payment.failed` · `payment.refunded`, og for et\ndepositum også `deposit.succeeded` + `balance.due`.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["booking_id","kind","method","amount_minor"],"properties":{"booking_id":{"type":"string"},"kind":{"type":"string","enum":["deposit","balance","adjustment","refund"]},"method":{"type":"string","enum":["manual_bank","ean_invoice","mobilepay","card","cash","terminal","giftcard"]},"amount_minor":{"type":"integer","description":"Øre, altid positivt."},"status":{"type":"string","enum":["pending","settled","failed"],"default":"settled"},"occurred_at":{"type":"string","format":"date-time"},"provider":{"type":["string","null"]},"provider_ref":{"type":["string","null"],"description":"UNIQUE sammen med `provider` og `kind` — gatewayens idempotens."},"note":{"type":["string","null"]}}}}}},"responses":{"201":{"description":"Registreret."},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{id}/payment_link":{"post":{"tags":["Penge"],"operationId":"createPaymentLink","summary":"Betalingslink til bookingens depositum","description":"SPEC 6.14 + ejerbeslutning §8 #4: *\"Onlinebetaling er pr. ydelse valgfri —\nfaktura-flowet (EAN/CVR) forbliver ligeværdigt, ikke en undtagelse.\"*\n\nOpretter — eller finder — den betalingsside kunden kan betale depositummet på.\nBeløbet er det der stadig SKYLDES: `deposit_minor` minus det der er registreret.\n\n**Kaldet svarer 200 også når svaret er nej,** og `status` siger hvilket nej:\n`ready` · `nothing_due` · `already_paid` · `not_online` (ydelsen opkræver ikke online)\n· `no_provider` (huset har ingen gateway) · `provider_failed`. Et 4xx her ville gøre\ndet til en fejltilstand at være en kommune, og SPEC 6.14 er hård på at en booking\nskal kunne bekræftes uden betaling.\n\n**Idempotent i sig selv.** Findes der allerede et gyldigt link på det skyldige beløb,\nkommer dét tilbage, og udbyderen får den samme idempotensnøgle — to samtidige klik\nbliver ikke til to opkrævninger.\n\nRækken der bærer linket er en `payment` med `status: \"pending\"`. Den tæller ikke som\nbetalt nogen steder; det gør den den dag udbyderens webhook siger at pengene kom.\n\nGæstens eget link (`lsbt_…`) må kalde endpointet for SIN booking. Det kan ikke andet\nend at pege på en opkrævning der allerede er aftalt.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"responses":{"200":{"description":"Linket, eller grunden til at der ikke er et."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/invoices":{"get":{"tags":["Penge"],"operationId":"listInvoices","summary":"Fakturaer og kreditnotaer","description":"SPEC 2.5: `invoice_line` findes fra bookingens oprettelse — `invoice` er hovedet der\nudstedes senere. Listen her er hovederne, hver med sine linjer, sin momsopstilling pr.\nsats og de to AFLEDTE tal: `totals.amount_due_minor` og `totals.payment_status`.\n\n*\"`amount_due = SUM(invoice_line.gross_minor) − SUM(payment.amount_minor WHERE\nstatus='settled')`. **Afledt, aldrig gemt.**\"* Der findes ingen `paid`-kolonne at læse,\nog der kommer ikke en.\n\n`?booking_id=` afgrænser til én booking. `?overdue=true` er SPEC 5.2's gemte filter\n*\"Forfaldne betalinger\"* — udstedt, frist passeret, og der mangler stadig penge.\n\nKræver modulet `invoicing` (SPEC 3 lag 5). Er det slået fra, svarer endpointet 404.","parameters":[{"name":"booking_id","in":"query","schema":{"type":"string"}},{"name":"overdue","in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Penge"],"operationId":"createInvoice","summary":"Udsted en faktura for en booking","description":"Samler **hver ufaktureret `invoice_line`** på bookingen under ét hoved og giver det det\nnæste nummer i organisationens serie. Der prissættes ikke noget her: linjerne findes\nallerede, snapshottet da bookingen blev lavet (aksiom 4).\n\n**Én undtagelse, og den er SPEC 6.13:** faktureringsgrundlaget er\n`max(guaranteed_count, actual_count)`. Er grundlaget højere end det antal linjerne blev\nprissat for, tilføjes forskellen som sin egen linje til samme effektive stykpris —\n*\"Garantital: 52 kuverter (fremmødt 45) — 4 ud over det prissatte antal på 48\"*. Den\nanden vej krediteres aldrig: **nedsættelser krediteres ikke.**\n\n**Nummerserien må aldrig få huller.** Nummeret reserveres i SAMME transaktion som\nhovedet skrives, så en rullet tilbage faktura tager sit nummer med sig, og to samtidige\nkald serialiseres på skrivelåsen. `Idempotency-Key` er påkrævet af samme grund.\n\n`ean` valideres som 13 cifre med EAN-13-kontrolciffer og `cvr` som 8 cifre med\nmodulus-11 (SPEC 6.14). Udelades de, tages kundens egne.\n\nEvent: `invoice.issued`.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["booking_id"],"properties":{"booking_id":{"type":"string"},"ean":{"type":"string","nullable":true,"description":"13 cifre, EAN-13."},"cvr":{"type":"string","nullable":true,"description":"8 cifre, modulus-11."},"reference":{"type":"string","nullable":true,"description":"Kundens egen rekvisition. En EAN-faktura afvises uden."},"due_days":{"type":"integer","description":"Betalingsfrist i dage. Standard: husets `payment.invoice_due_days`, ellers 30."}}}}}},"responses":{"201":{"description":"Fakturaen er udstedt."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/invoices/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Penge"],"operationId":"getInvoice","summary":"Hele fakturaen med moms pr. sats","description":"Hoved, linjer, `totals.vat_breakdown` med én række pr. momssats, og de afledte\n`amount_due_minor` / `payment_status`. Det er præcis dét dokument `/admin/faktura/[id]`\ntrykker — arket og API-svaret kan ikke drive fra hinanden (aksiom 7).\n\nEn kreditnota er samme objekt med `kind: \"credit_note\"`, negative linjer og\n`credit_of_invoice_id` sat.","responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/invoices/{id}/send":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Penge"],"operationId":"sendInvoice","summary":"Send fakturaen til kunden","description":"SPEC 5.3: *\"eksplicit knap på kommunikation … systemet sender aldrig en mail bag ryggen\npå nogen.\"* At udstede og at sende er to beslutninger, og kun den ene forlader huset —\nsamme opdeling som køreplanens.\n\nModtageren er `to`, ellers kundens e-mail, ellers bookingens kontakt. Brevet er ren\ntekst med momsopstillingen pr. sats, fordi modtageren ofte er en delt kommunal postkasse\nder fjerner HTML.\n\nSPEC 6.15's dedup gælder: samme faktura til samme adresse sendes én gang. `sent_at`\nsættes kun når adapteren tog imod den.\n\nEvent: `invoice.sent`.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","description":"Overstyrer kundens egen adresse."}}}}}},"responses":{"200":{"description":"Sendt (eller allerede sendt — se `message`)."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reports/occupancy":{"get":{"tags":["Drift"],"operationId":"getOccupancyReport","summary":"Belægning pr. ressource pr. uge","description":"Additiv i forhold til SPEC 4.4's liste (tilladt jf. §4.3) og bygget fordi skærmen\n`/admin/bookinger/belaegning` er admin-UI, og aksiom 7 siger at admin-UI kalder\nAPI-handlerne. Alternativet — en serverkomponent der selv læste `resource_allocation` og\ngenudledte åbningstiderne — ville være en anden mening om hvornår huset har åbent.\n\n**Nævneren er åbningstiden**, ikke døgnet: en sal booket 09–16 en tirsdag er fuld for\nden der skal sælge onsdag, og 29 % af et kalenderdøgn for ingen. Åbningstiden er\nlokationens skema ∩ ressourcens skema (SPEC 6.9).\n\n**Tælleren er den BLOKEREDE tid** — bufferen tæller med, for den kan ikke sælges\n(SPEC 6.7) — og kun hårde blokeringer (`block_level = 2`). En option er et løfte nogen\nmåske ikke holder, og at tælle den ville få huset til at se fuldt ud på forespørgsler.\n\nOptaget tid klippes til de åbne vinduer, så en uge aldrig kan komme over 100 %.\nUgenøglen er ISO (`2026-W31`).","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date-time"},"description":"RFC 3339 MED offset (SPEC 4.2). Standard: nu."},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Eksklusiv. Standard: fire uger frem. Højst et år."},{"name":"location_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reports/revenue":{"get":{"tags":["Rapporter"],"operationId":"getRevenueReport","summary":"Omsætning pr. måned, ydelse og kilde","description":"SPEC §7 fase 2, beslutning #28: *«rapporter skal besvare pengespørgsmål»*. Additiv i\nforhold til §4.4's liste (tilladt jf. §4.3). Bag modulet `reports`.\n\n**Grundlaget er `invoice_line`, ikke en genberegning.** SPEC §2.5: linjen findes fra\nbookingens oprettelse, længe før nogen faktura er udstedt, og den bærer\nmomssnapshottet. Tallene her er derfor de samme som står på kundens faktura — ikke et\nandet tal for det samme salg.\n\n**Perioden er hvornår arrangementet LIGGER** (`booking.starts_at`), ikke hvornår det\nblev solgt. Et hus planlægger kapacitet pr. uge.\n\n**Kun realiseret:** `confirmed`, `completed` og `no_show`. En option er ikke omsætning.\nEn udeblivelse ER omsætning — SPEC §6.13 fakturerer `max(guaranteed, actual)`, og maden\nblev lavet. Aflysninger opgøres i `/reports/cancellations` og ikke her, fordi en aflyst\nbooking der aldrig blev faktureret, ellers ville tælle fuldt med.\n\n`invoiced_minor` er den del af bruttoen der ligger på et udstedt fakturahoved;\n`uninvoiced_minor` er resten, og den er en arbejdsliste. `outstanding_minor` er brutto\nminus afregnede betalinger (`payment.status = 'settled'`).\n\nScopet er `reports:read` ALENE — omsætning pr. ydelse er husets forretningshemmelighed,\nog en nøgle der må læse bookinger skal ikke dermed have fået den.","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date-time"},"description":"RFC 3339 MED offset (SPEC 4.2). Standard: nu."},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Eksklusiv. Standard: fire uger frem. Højst et år."},{"name":"location_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reports/cancellations":{"get":{"tags":["Rapporter"],"operationId":"getCancellationReport","summary":"Aflysninger, årsager, udeblivelser og tabt omsætning","description":"SPEC §7 fase 2 og §6.11. Bag modulet `reports`.\n\n**Årsagerne er husets egne rækker** i `cancel_reason`, ikke en enum i koden. To\nkolonner dér bærer hele rapporten: `is_no_show` skiller «de kom ikke» fra «de meldte\nfra» (SPEC §6.8 [H] — aldrig samme ting), og `counts_as_lost_revenue` er husets eget\nsvar på om tabet er ægte. En booking der blev flyttet, er ikke tabt omsætning.\n\n**`lost_gross_minor` er bookingens grundlag FØR krediteringen.** En aflyst booking der\nnåede at blive faktureret, får en kreditnota med negative `invoice_line`-rækker; en rå\nsum ville derfor give nul — «vi mistede ingenting» om det arrangement der kostede mest.\nLinjer der hænger på en kreditnota tælles ikke med.\n\n`no_show_pct` har REALISEREDE bookinger som nævner, ikke alle. En procent af et tal der\nogså indeholdt forespørgsler og optioner, ville falde når salget gik godt.","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"location_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reports/customers":{"get":{"tags":["Rapporter"],"operationId":"getCustomerReport","summary":"Største kunder, gentagne og nye pr. måned","description":"SPEC §7 fase 2. Bag modulet `reports`.\n\n**Kræver BÅDE `reports:read` og `customers:read`** — rækkerne bærer kundenavne, og\nSPEC §4.1 #9 siger at en nøgle der må se husets belægning ikke dermed har fået\nkundelisten. Det er «begge», ikke «en af dem».\n\nToppen er sorteret efter OMSÆTNING, ikke antal: et hus har altid en kunde der booker\nmødelokalet hver tirsdag og en der holder ét årsmøde til fire hundrede tusind.\n\n`returning` betyder «havde en booking før perioden» — kundeforholdets alder, ikke\naktiviteten i vinduet. `new_by_month` tæller kunder hvis FØRSTE booking nogensinde\nligger i måneden; det er det ene tal der ikke kan udledes af vinduet alene.\n\nBookinger uden `customer_id` (taget i røret) står som `without_customer`: det er\nomsætning, men ikke en kunde, og en top-ti må ikke blive en telefonbog.","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":25},"description":"Hvor mange kunder i toppen. Totalerne dækker altid alle."},{"name":"location_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/shifts":{"get":{"tags":["Vagtplan"],"operationId":"listShifts","summary":"Vagter — og vejen et lønsystem henter timerne","description":"SPEC §1.3's ejer-ændring 2026-07-26. Bag modulet `staffing` (SPEC §3 lag 5), som er\nslået FRA som standard: er det ikke tændt, svarer endpointet 404 med en sætning om\nhvor kontakten sidder — ikke 402, og ikke en tom liste.\n\n**Én række = ét menneske på arbejde i ét tidsrum.** Ressourcen er medarbejderen\n(`resource_type.is_staff = 1`); der findes ingen «employee»-tabel ved siden af\n`resource`. Skal der tre tjenere på om lørdagen, er det tre vagter.\n\n**Tillægsrubrikkerne står i svaret, men ikke i databasen.** `hours` beregnes hver gang\nud fra klokkeslæt og husets egen `holiday`-tabel (aksiom 1): dag 06–18, aften 18–24,\nnat 24–06, og `holiday_minutes` er hvor mange af DE SAMME timer der lå på en helligdag.\nLæg dem ikke sammen — hvad et helligdagstillæg gør ved en aftentime, afgør\noverenskomsten, og den kender vi ikke. Der er ingen satser og ingen beløb: vi beregner\nikke løn (SPEC §1.3).\n\nLønsystemets kald er `?approved=true&period=2026-07`. Abonnér på `shift.approved` for\nat høre hvornår der er noget nyt at hente.","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date-time"},"description":"RFC 3339 MED offset (SPEC 4.2)."},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"resource_id","in":"query","schema":{"type":"string"}},{"name":"approved","in":"query","schema":{"type":"boolean"},"description":"Kun godkendte (`true`) eller kun det der mangler godkendelse (`false`)."},{"name":"period","in":"query","schema":{"type":"string","example":"2026-07"},"description":"Lønperioden. Sættes på vagten når den godkendes."},{"name":"limit","in":"query","schema":{"type":"integer","default":200,"maximum":1000}}],"responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Vagtplan"],"operationId":"createShift","summary":"Læg en vagt ind","description":"Ressourcen skal være personale. En vagt på et lokale ville få bemandingskurven til at\ntælle en sal med som en tjener, og svaret siger det i stedet for at gemme det.\n\n`actual_start`/`actual_end` er et PAR og udfyldes manuelt — der er ingen\ntidsregistrering i produktet, og der kommer ikke en i fase 2 (SPEC §1.3). Er de tomme,\ner planen dét der tæller.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["resource_id","planned_start","planned_end"],"properties":{"resource_id":{"type":"string"},"planned_start":{"type":"string","format":"date-time"},"planned_end":{"type":"string","format":"date-time"},"actual_start":{"type":"string","format":"date-time","nullable":true},"actual_end":{"type":"string","format":"date-time","nullable":true},"break_minutes":{"type":"integer","minimum":0,"description":"Ubetalt pause."},"note":{"type":"string","nullable":true}}}}}},"responses":{"201":{"description":"Oprettet"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/shifts/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Vagtplan"],"operationId":"getShift","summary":"Én vagt","responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Vagtplan"],"operationId":"updateShift","summary":"Ret en vagt","description":"En GODKENDT vagt kan ikke rettes — den er et bilag i en lønperiode. Svaret er 409 `invalid_transition`, og vejen er `POST /v1/shifts/{id}/reopen`, som står i `audit_log` med et navn på.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Vagtplan"],"operationId":"archiveShift","summary":"Tag vagten ud af planen","description":"Sletter ikke (SPEC §2.6 #6): rækken arkiveres. «Hvorfor stod der ingen på om lørdagen» skal kunne besvares et år senere.","responses":{"204":{"description":"Arkiveret"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/shifts/approve":{"post":{"tags":["Vagtplan"],"operationId":"approveShifts","summary":"Godkend én vagt eller en hel uge","description":"SPEC §1.3: *\"vagter har en godkendt-tilstand med audit\"*. Godkendelsen skriver\n`audit_log` og hændelsen `shift.approved` i SAMME transaktion som ændringen, og sætter\nlønperioden ud fra vagtens dato (`2026-07`).\n\nÉt endpoint til begge former, fordi det er den samme handling. Allerede godkendte\nvagter tælles i `already_approved` og springes over — at trykke to gange er ikke en\nfejl.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"}},"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"resource_id":{"type":"string","nullable":true}},"description":"Enten `ids`, eller et tidsrum (`from` + `to`)."}}}},"responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/shifts/{id}/reopen":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Vagtplan"],"operationId":"reopenShift","summary":"Tag en godkendelse tilbage","description":"Den eneste vej til at rette en godkendt vagt. Der udsendes ingen hændelse: hører et system `shift.approved` to gange for den samme vagt, er dét beskeden om at noget blev rettet.","responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/shifts/export":{"get":{"tags":["Vagtplan"],"operationId":"exportShifts","summary":"Lønperioden som CSV","description":"SPEC §1.3: *\"godkendte timer kan eksporteres pr. periode som CSV\"*. Kun det godkendte\nkommer med — en fil der også bærer det ingen har set på, kan ikke bruges som bilag.\n\nSemikolon, dansk komma og BOM, fordi filen bliver åbnet i et dansk Excel før den bliver\nlæst af noget andet. Kolonner: medarbejder · lønnummer · dato · start · slut ·\npause_min · timer_i_alt · timer_dag · timer_aften · timer_nat · timer_helligdag ·\ntid_kilde · note.\n\n`timer_helligdag` er de SAMME timer som står i dag/aften/nat, markeret fordi de lå på\nen dato i `holiday`. Der står ingen kroner i filen.","parameters":[{"name":"period","in":"query","schema":{"type":"string","example":"2026-07"}},{"name":"from","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"resource_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"CSV","content":{"text/csv":{"schema":{"type":"string"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/staffing/coverage":{"get":{"tags":["Vagtplan"],"operationId":"getStaffingCoverage","summary":"Bemandingsbehov mod planlagte vagter, time for time","description":"SPEC §1.3: modulet *\"viser bemandings-behovet fra bookingernes allokeringer over for de\nplanlagte vagter — huller og overbemanding synlige pr. dag\"*. Et opslag, ikke en tabel:\nkurven er altid regnet på de bookinger der findes lige nu (aksiom 1).\n\nBehovet tælles pr. RESSOURCETYPE, og `units` på allokeringen ER antallet af mennesker\n(SPEC §2.3: en pulje med `capacity 12` er tolv tjenere). Det SPÆRREDE interval tæller,\nikke gæstetiden: bufferen på en personaleressource er opstilling og oprydning, og det\ner arbejde nogen skal møde ind til.\n\n`required` er de bekræftede bookinger, `tentative` er optionerne — talt hver for sig,\nfor en vagtplan der bemander efter optioner, sender folk hjem igen. `gap` regnes kun på\n`required`.","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date-time"},"description":"RFC 3339 MED offset. Standard: nu."},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Eksklusiv. Standard: en uge frem. Højst to måneder."},{"name":"location_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/staffing/periods":{"get":{"tags":["Vagtplan"],"operationId":"listPayrollPeriods","summary":"De lønperioder der har godkendte timer","description":"Så en eksport kan tilbyde et valg frem for et tomt tekstfelt (SPEC §5.5 #3).","responses":{"200":{"description":"OK"},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/price_rules":{"get":{"tags":["Penge"],"operationId":"listPriceRules","summary":"Prisregler, i den rækkefølge de køres","description":"SPEC 6.12: en pris er en **prioriteret regelliste**, ikke et tal på ydelsen. Derfor har\n`offering` ingen `price`-kolonne: den samme konferencedag koster ét på en tirsdag for otte\nog noget andet på en lørdag for fyrre, og en kolonne ville tvinge hvert hus i landet til at\nmodellere den forskel som separate produkter.\n\nSorteret på `priority` **stigende** og evalueret i den orden: grundbeløb lavt, tillæg og\nrabatter højere. `?offering_id=` inkluderer altid de globale regler (`offering_id: null`),\nfor de gælder også den ydelse — en liste uden dem ville ikke være prisen.\n\n`?include=vocabulary` svarer med hele det lukkede DSL: felter, operatorer og effekttyper.\nBrug det frem for at holde din egen kopi — den kan drive fra vores.\n\nKræver modulet `pricing` (SPEC 3 lag 5). Er det slukket, svares 404 med sætningen der\nsiger hvor kontakten sidder.","parameters":[{"name":"offering_id","in":"query","schema":{"type":"string"}},{"name":"include","in":"query","schema":{"type":"string","enum":["vocabulary"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceRuleList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Penge"],"operationId":"createPriceRule","summary":"Opret prisregel","description":"`condition` og `effect` valideres mod det samme lukkede DSL motoren bruger — mod en\nprøvekontekst, så et stavefejlet feltnavn afvises HER og ikke som en 500 på en\nlørdagsbooking om tre uger. En regel der ikke kan evalueres, kan ikke gemmes.\n\nSPEC 6.15: skrivningen og dens audit-række deler én transaktion. \"Hvem satte kapaciteten ned\ntil 60\" bliver spurgt om i uge to.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceRuleInput"}}}},"responses":{"200":{"description":"Fandtes allerede via `external_ref` (SPEC 4.8).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceRule"}}}},"201":{"description":"Oprettet.","headers":{"ETag":{"description":"Objektets `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceRule"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/price_rules/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Penge"],"operationId":"getPriceRule","summary":"Prisregel","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Objektets `updated_at` — tokenet PATCH kræver.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceRule"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Penge"],"operationId":"updatePriceRule","summary":"Ret prisregel — kræver If-Match","description":"Hele reglen genvalideres, ikke kun de felter der ændrede sig: en effekt og dens betingelse\ner ét udsagn, og et halvt valideret udsagn er hvordan en uevaluerbar regel kommer i hus.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceRuleInput"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceRule"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Penge"],"operationId":"deletePriceRule","summary":"Slet prisreglen","description":"Fjerner rækken. En prisregel er en regel **i kraft**, ikke noget der skete: hvad kunden\nfaktisk blev opkrævet ligger snapshottet på linjens `price_explanation_json` (aksiom 4),\nså \"hvorfor var det 47.610 kr\" kan stadig besvares. En gravsten ville derfor ikke købe\nnoget og koste motoren en række at springe over for altid. Samme argument som\n`schedule_exception` og `holiday`.","responses":{"204":{"description":"Slettet."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/quotes":{"post":{"tags":["Penge"],"operationId":"createQuote","summary":"Fuld prisopsplitning uden at booke","description":"SPEC 4.4. Ét endpoint, tre kaldere, og dét er pointen (aksiom 7): opsætningsskærmens\neksempelberegning, den offentlige flades pris på hver tidsknap og en integrator der\nspørger hvad en lørdag koster, får alle det samme tal fra de samme regler.\n\nSvaret har form som **bookingens pengehalvdel** — `totals` plus\n`lines[].price_explanation` — så den samme komponent forklarer et tilbud og en booket\nlinje, og \"Hvorfor?\" er det samme spørgsmål begge steder (SPEC 6.12).\n\nDer holdes ingenting og skrives ingenting. Et tilbud er ikke et hold: `slot_hold`\n(SPEC 6.5) er objektet der reserverer tid, og at blande dem sammen er hvordan et\npristjek begynder at blokere kalenderen.\n\n`skipped` bærer de regler der blev set på og ikke gjaldt, med grunden. Det er den\nhalvdel der besvarer \"hvorfor slog weekendtillægget ikke til?\".\n\n`channel` afgøres af legitimationen, aldrig af body — en gæstenøgle kan ikke bede om\nadmin-prisen.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteInput"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/participant_categories/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Katalog"],"operationId":"getParticipantCategory","summary":"Deltagerkategori","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Objektets `updated_at` — tokenet PATCH kræver.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantCategory"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Katalog"],"operationId":"updateParticipantCategory","summary":"Ret deltagerkategori — kræver If-Match","description":"`key` kan ikke ændres: den står på hver eneste `booking_line_pax`-række og i enhver\nprisregel der nævner kategorien.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantCategoryInput"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantCategory"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Katalog"],"operationId":"archiveParticipantCategory","summary":"Arkivér deltagerkategori (DELETE = arkivering)","description":"SPEC 2.6 #6 / 6.15: **intet slettes.** `DELETE` sætter `archived_at`, og objektet kommer\ntilbage i svaret med `archived: true`.\n\nArkivering afvises så længe der stadig ligger FREMTIDIGE blokerende allokeringer på objektet —\nog afvisningen bærer listen over dem i `alternatives`. SPEC 6.15 formulerer det selv\n(\"afvises med listen over dem\"), og SPEC 4.7 kræver at en 409 altid peger videre.\n\nAflyste bookinger tæller ikke med: deres allokeringsrække bliver liggende for altid på\n`block_level = 0` (SPEC 2.3), så \"hvorfor var lokalet optaget den dag\" kan besvares et år\nsenere — uden at spærre for at arkivere lokalet.","responses":{"200":{"description":"Arkiveret (eller allerede arkiveret).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantCategory"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/field_defs/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Katalog"],"operationId":"getFieldDef","summary":"Spørgsmål","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Objektets `updated_at` — tokenet PATCH kræver.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldDef"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Katalog"],"operationId":"updateFieldDef","summary":"Ret spørgsmål — kræver If-Match","description":"**`entity` og `key` er ikke i patch-skemaet.** Nøglen er feltnavnet inde i hvert eneste\n`custom`-objekt der allerede er skrevet: at omdøbe den omdøber ikke svarene, den forældreløser\ndem — stille, på bookinger ingen åbner i dag. Etiketten er det et menneske læser, og den\nflytter frit.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldDefInput"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldDef"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Katalog"],"operationId":"archiveFieldDef","summary":"Arkivér spørgsmål (DELETE = arkivering)","description":"SPEC 2.6 #6 / 6.15: **intet slettes.** `DELETE` sætter `archived_at`, og objektet kommer\ntilbage i svaret med `archived: true`.\n\nArkivering afvises så længe der stadig ligger FREMTIDIGE blokerende allokeringer på objektet —\nog afvisningen bærer listen over dem i `alternatives`. SPEC 6.15 formulerer det selv\n(\"afvises med listen over dem\"), og SPEC 4.7 kræver at en 409 altid peger videre.\n\nAflyste bookinger tæller ikke med: deres allokeringsrække bliver liggende for altid på\n`block_level = 0` (SPEC 2.3), så \"hvorfor var lokalet optaget den dag\" kan besvares et år\nsenere — uden at spærre for at arkivere lokalet.","responses":{"200":{"description":"Arkiveret (eller allerede arkiveret).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldDef"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/document_previews":{"post":{"tags":["Drift"],"operationId":"createDocumentPreview","summary":"Tegn et dokument med en given skabelon, uden at gemme noget","description":"SPEC §5.3 #4's *\"levende forhåndsvisning\"*, for dokumenter — og motoren under\nskabelon-editoren i Opsætning › Dokumenter (SPEC §7 fase 2).\n\nByg `quote`, `beo`, `invoice` eller `confirmation_mail` på en RIGTIG booking, med en\nskabelon der ikke er gemt endnu. Det er de samme indholdsbyggere som det rigtige\ndokument bruger (aksiom 7): tegnede skærmen selv sin forhåndsvisning, ville der findes\nto opskrifter på et tilbud, og de ville blive uenige første gang den ene fik en linje mere.\n\n**Der skrives ingenting.** Ingen version, ingen række, ingen hændelse, og aldrig et\nnummer ud af fakturaserien. En forhåndsvisning er et spørgsmål, ikke en beslutning —\nsamme skel som `POST /v1/quotes` (pris uden at booke) holder ét lag længere nede.\n\nUdelades `booking_id`, bruges husets nyeste booking. Har huset ingen, svarer endpointet\n200 med `booking_id: null` og `content: null`: et nyt hus uden bookinger er en tilstand,\nikke en fejl.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["quote","beo","invoice","confirmation_mail"]},"booking_id":{"type":"string","description":"Udeladt = husets nyeste booking."},"template":{"$ref":"#/components/schemas/DocumentTemplate","description":"Den ugemte skabelon. Udeladt = husets gemte, altså \"sådan ser den ud lige nu\"."}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"const":"document_preview"},"type":{"type":"string"},"booking_id":{"type":["string","null"]},"booking":{"type":"object","properties":{"reference":{"type":"string"},"customer":{"type":["string","null"]},"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"RFC 3339 med offset, i lokationens zone (SPEC 4.2, 6.10)."}}},"content":{"description":"Typens eget indhold: `QuoteSnapshot`, `BeoSnapshot`, et fakturaomrids eller `{subject, text}` for mailen."}}}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/message_templates":{"get":{"tags":["Drift"],"operationId":"listMessageTemplates","summary":"De fem beskeder og deres timing","description":"SPEC 3 lag 4: *\"Fase 1 har fem faste regler med justerbar timing.\"* Nøglelisten er lukket\n— `booking.confirmed`, `booking.reminder`, `booking.option_expiring`,\n`booking.guarantee_due`, `booking.cancelled` — så endpointet ikke ved et uheld bliver en\nregelbygger. Fuld `automation` er fase 2, og halvdelen af den er en triggerliste intet fyrer.\n\n`offset_min` er timingen, målt fra reglens eget ANKER og negativ for \"før\":\n`-1440` på `booking.reminder` er dagen før start. De to der fyrer på en tilstandsændring\nhar intet anker og dermed ingen timing.\n\n**Afsendelsen findes (M6), og svaret siger hvordan:** `delivery_enabled: true`.\nDen interne planlægger ser efter forfaldne beskeder hvert 5. minut og sender de regler\nder er slået til — gennem `message_log`-dedup og statustjekket ved afsendelse (SPEC 6.15).\nEndepunktet her sender aldrig selv; det gemmer teksten og timingen.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTemplateList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Drift"],"operationId":"createMessageTemplate","summary":"Opret besked","description":"`{{…}}`-pladsholdere valideres mod en lukket liste og erstattes ved udskiftning — der køres\ningen kode (SPEC 3 lag 4: \"Ingen `eval`, ingen kundekode i vores proces\").\n\n`channel: \"sms\"` kræver modulet `sms`.\n\nSPEC 6.15: skrivningen og dens audit-række deler én transaktion. \"Hvem satte kapaciteten ned\ntil 60\" bliver spurgt om i uge to.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTemplateInput"}}}},"responses":{"200":{"description":"Fandtes allerede via `external_ref` (SPEC 4.8).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTemplate"}}}},"201":{"description":"Oprettet.","headers":{"ETag":{"description":"Objektets `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTemplate"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/message_templates/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Drift"],"operationId":"getMessageTemplate","summary":"Besked","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Objektets `updated_at` — tokenet PATCH kræver.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTemplate"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Drift"],"operationId":"updateMessageTemplate","summary":"Ret besked — kræver If-Match","description":"`key` og `channel` er ikke patchbare: de ER identiteten på en af de fem regler, og at flytte\ndem ville i stilhed pege husets bekræftelsestekst på dets aflysning.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTemplatePatch"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTemplate"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Drift"],"operationId":"deleteMessageTemplate","summary":"Fjern beskeden","description":"Fjerner rækken. Hvad der faktisk BLEV sendt, ligger i `message_log` (SPEC 6.15). Den blødere handling — og den skærmen tilbyder først — er `is_active: false`: teksten bliver, reglen holder op med at fyre.","responses":{"204":{"description":"Fjernet."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/settings":{"get":{"tags":["Drift"],"operationId":"getSettings","summary":"Husets indstillinger — moduler, betaling og bookingside","description":"Ét dokument over `tenant_setting`, ikke elleve endpoints opkaldt efter elleve nøgler.\nTabellen er key/value, hvilket er rigtigt for en håndfuld husvide kontakter og forkert\nfor en API-flade: elleve handlere ville være elleve steder at glemme audit-rækken.\n\n**`modules` er den med tænder.** SPEC 3 lag 5: *\"Alt der ikke er slået til findes ikke i\nUI'et. Ingen tomme menupunkter, ingen 'opgradér for at se'.\"* At slukke `pricing` gør\nikke prisskærmen grå — den forlader navigationen, og `/v1/price_rules` svarer 404 med en\nsætning der navngiver kontakten. Den symmetri er hele pointen: et slukket modul er slukket\noveralt, også for en integrator.\n\n`booking` kan ikke slås fra. Alt andet er et stykke af produktet et hus måske ikke har\nkøbt; uden booking er der intet produkt tilbage.\n\nETag er den nyeste `updated_at` i dokumentet, så to der retter huset samme morgen får\nsamme 412 som på et lokale.","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Nyeste `updated_at` i dokumentet.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Settings"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Drift"],"operationId":"updateSettings","summary":"Ret indstillinger — kræver If-Match","description":"Delvis: kun de nøgler der er med i body røres. `payment` og `booking_page` flettes ind i\ndet der står i forvejen, `modules` erstattes helt (og sorteres, så en diff af to audit-rækker\nlæses som en diff og ikke som en omrokering).\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettingsPatch"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Settings"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/terms":{"get":{"tags":["Drift"],"operationId":"getTerms","summary":"Husets ord for systemets begreber","description":"SPEC §3 lag 3: fyrre systemnøgler med fire danske former hver — ubestemt og bestemt,\nental og flertal — plus en udledt genitiv og et udledt køn.\n\n**Det her oversætter ikke API-fladen.** Feltnavne og enums er engelske og bliver det\n(SPEC 4.2). En booking har stadig `\"status\": \"confirmed\"` i et hus der kalder den et\narrangement. Endpointet findes fordi en integrator der bygger sin EGEN skærm, skal kunne\nskrive de samme ord som huset selv ser — ikke fordi protokollen følger med.\n\nSvaret siger for hver nøgle hvor ordet kommer fra: `system` (vores), `blueprint`\n(branchepakkens) eller `tenant` (husets eget). `default_forms` er hvad en nulstilling\nville give — pakkens ord hvis den har et, ellers systemets.\n\nOrdene kan bruges i beskedskabeloner som `{{word_booking}}`, `{{word_booking_definite}}`\nog `{{word_booking_plural}}`. Præfikset er ikke pynt: `{{offering}}` betyder allerede\nydelsens NAVN på den enkelte booking.","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Nyeste `updated_at` i dokumentet.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Terms"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Drift"],"operationId":"updateTerms","summary":"Ret husets ord — kræver If-Match","description":"Delvis: kun de nøgler der er med i body røres.\n\n**`null` på en nøgle nulstiller den.** Rækken slettes, og ordet kommer igen fra pakken\n(eller fra systemet). Det er derfor der ikke findes et `DELETE /terms/{key}`: at rette og\nat nulstille er den samme håndbevægelse på den samme skærm, og to endpoints ville være to\nsteder at glemme audit-rækken.\n\nKøn indtastes ikke. Det udledes af bestemt ental, som i dansk altid ender på `-t` i\nintetkøn og `-n` i fælleskøn.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TermsPatch"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Terms"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/users":{"get":{"tags":["Drift"],"operationId":"listUsers","summary":"Hvem der har adgang","description":"SPEC 7: to roller i fase 1, `admin` og `staff`, og rollestyring ud over dem er fase 2.\nForskellen er reel og håndhæves ét sted (`SESSION_SCOPES`): *medarbejder* er de samme\nskærme minus dem der præger legitimationer eller flytter penge ud.\n\nDeaktiverede kolleger er med i listen, nederst: \"hvem havde adgang\" er et spørgsmål i\ndatid, og at skjule dem får svaret til at se ud som om ingen havde.\n\nScope: `users:manage`, som kun en admin-session og en fuld hemmelig nøgle bærer.","parameters":[{"name":"archived","in":"query","schema":{"type":"string","enum":["false"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Drift"],"operationId":"createUser","summary":"Opret bruger","description":"**Adgangskoden vælges ikke her af en anden.** En oprettet bruger får som udgangspunkt slet\ningen — `has_password: false` er tilstanden, og administratoren giver dem én ad anden vej.\nEt endpoint hvor ét menneske sætter et andet menneskes kodeord er et endpoint der før eller\nsiden logger ind som dem, og audit-loggen ville sige det forkerte navn.\n\nSPEC 6.15: skrivningen og dens audit-række deler én transaktion. \"Hvem satte kapaciteten ned\ntil 60\" bliver spurgt om i uge to.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInput"}}}},"responses":{"200":{"description":"Fandtes allerede via `external_ref` (SPEC 4.8).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"201":{"description":"Oprettet.","headers":{"ETag":{"description":"Objektets `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/users/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Drift"],"operationId":"getUser","summary":"Bruger","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Objektets `updated_at` — tokenet PATCH kræver.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Drift"],"operationId":"updateUser","summary":"Ret bruger — kræver If-Match","description":"**Den sidste administrator kan hverken degraderes eller deaktiveres.** Et hus med nul\nadministratorer kan ikke oprette én, og der findes ingen bagdør via support (SPEC 5.7).\n\n**En rolleændring tilbagekalder sessionerne.** Scopes slås op når en session autentificerer,\nså en degraderet admin skal forbi login igen for at få det mindre sæt.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPatch"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Drift"],"operationId":"deactivateUser","summary":"Fjern adgang (DELETE = deaktivering)","description":"SPEC 2.6 #6: `archived_at`, aldrig en fjernet række. `audit_log` og `booking_event` bærer `actor_id`, og \"hvem flyttede den booking\" bliver spurgt om i uge to — en slettet række gør det svar til et id. Sessionerne tilbagekaldes med det samme.","responses":{"200":{"description":"Deaktiveret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/roles":{"get":{"tags":["Drift"],"operationId":"listRoles","summary":"Roller — hvad hver slags kollega må","description":"SPEC §7 fase 2: *rollestyring ud over admin/medarbejder*. En rolle er en RÆKKE, ikke en\ngren i koden — grundreglen i §3 siger at et nyt behov bliver et generisk primitiv, aldrig en\nbetingelse på hvem der spørger.\n\nRettighederne er ordforrådet (koden ejer listen, huset ejer hvem der har hvad), og de\noversættes til de SAMME scopes en API-nøgle bærer. Derfor findes der én håndhævelsesvej\nog ikke to (aksiom 7, SPEC 4.1 #9) — `scopes` på svaret ER den oversættelse, skrevet ud så\nman kan se det.\n\n`admin` og `staff` følger med hvert hus. `admin` kan omdøbes, men ikke skæres ned: et hus\nhvor ingen må oprette brugere, er et hus ingen kan komme ind i igen (SPEC 5.7).\n\nScope: `users:manage`.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Drift"],"operationId":"createRole","summary":"Opret rolle","description":"Nøglen udledes af etiketten hvis den ikke sendes. Ukendte rettigheder falder fra i stedet\nfor at afvise kaldet — additivt, som SPEC 4.1 #5 kræver af alt andet i v1.\n\nSPEC 6.15: skrivningen og dens audit-række deler én transaktion. \"Hvem satte kapaciteten ned\ntil 60\" bliver spurgt om i uge to.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleInput"}}}},"responses":{"200":{"description":"Fandtes allerede via `external_ref` (SPEC 4.8).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}}},"201":{"description":"Oprettet.","headers":{"ETag":{"description":"Objektets `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/roles/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Drift"],"operationId":"getRole","summary":"Rolle","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Objektets `updated_at` — tokenet PATCH kræver.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Drift"],"operationId":"updateRole","summary":"Ret rolle — kræver If-Match","description":"**Administratorrollens rettigheder kan ikke ændres.** Etiketten må gerne. Uden en rolle der\nmå oprette brugere, kan huset ikke redde sig selv, og der er ingen bagdør.\n\n**En rettighedsændring logger de berørte ud.** Scopes slås op når en session\nautentificerer, så en indskrænket rolle bider først når de gamle sessioner er væk.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RolePatch"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Drift"],"operationId":"archiveRole","summary":"Luk rolle (DELETE = arkivering)","description":"SPEC 2.6 #6: `archived_at`, aldrig en fjernet række — rollen står stadig i audit-loggen. Afvises mens nogen har den, og med tallet: en kollega der pludselig ikke kan noget mandag morgen er en dyrere fejl end et ekstra klik.","responses":{"200":{"description":"Arkiveret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api_keys":{"get":{"tags":["Drift"],"operationId":"listApiKeys","summary":"Nøgler — `last_four` og `last_used_at`, aldrig nøglen selv","description":"SPEC 1.2 #2: *\"**API'et er produktet.** Selvbetjent nøgle på fem minutter.\"* Det er hele\ngrunden til at endpointet findes: et system hvor man skal skrive til support for at få en\nnøgle er \"kontakt din konsulent for API-adgang\", og det er den tydeligste ting vi gør anderledes.\n\nTilbagekaldte nøgler bliver stående, nederst. \"Hvilken nøgle var det der hamrede på os i\ntirsdags\" er et spørgsmål om en nøgle der ikke længere virker.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Drift"],"operationId":"createApiKey","summary":"Lav en nøgle — vises ÉN gang","description":"SPEC 4.2. **Klarteksten returneres præcis én gang, af denne handler, og aldrig igen.**\nRækken gemmer `sha256(nøgle)` plus `last_four`, så et databasedump ikke er et sæt levende\nlegitimationer, og \"jeg har mistet nøglen\" har ét svar: tilbagekald den og lav en ny.\nSvaret bærer `shown_once: true`, så en klient ikke kan tage fejl af feltet.\n\nEn `publishable`-nøgle ligger i gæstens browser, hvor alle kan læse den. Den kan derfor kun\nfå `catalog:read`, `availability:read`, `holds:write` og `bookings:create` — alt andet afvises.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyInput"}}}},"responses":{"201":{"description":"Oprettet — indeholder `key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api_keys/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Drift"],"operationId":"getApiKey","summary":"API-nøgle","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Objektets `updated_at` — tokenet PATCH kræver.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Drift"],"operationId":"updateApiKeyOrigins","summary":"Ret nøglens origin-allowlist — og kun den","description":"SPEC 4.2. **Det eneste felt der kan ændres på en udstedt nøgle.**\n\nScopes kan ikke: de er dét en kalder fik udleveret og byggede imod, og stille at udvide\neller indsnævre dem gør \"integrationen holdt op med at virke\" til et mysterium uden en\nhændelse at pege på. Der tilbagekalder man og udsteder en ny.\n\nOrigin-listen er den modsatte slags felt. Den er en sikkerhedskontrol huset STRAMMER over\ntid, på en nøgle der allerede står i deres hjemmeside — og at kræve en ny nøgle for at\ntilføje `www.` foran deres eget domæne ville betyde at redigere den live side og tage\nbookingflowet ned imens. Det er sådan en allowlist ender med at være tom for evigt.\n\nKun `publishable`-nøgler: en hemmelig nøgle sendes fra en server, der ikke har nogen\n`Origin` at holde op mod en liste. Kræver `If-Match` som alt andet der kan rettes\n(SPEC 4.4), og hver ændring er en audit-række med den gamle og den nye liste.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyOriginPatch"}}}},"responses":{"200":{"description":"Opdateret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Drift"],"operationId":"revokeApiKey","summary":"Tilbagekald nøglen","description":"PATCH kan kun røre origin-listen. En nøgles scopes er dét en kalder fik udleveret og\nbyggede imod; stille at udvide eller indsnævre dem gør \"integrationen holdt op med at\nvirke\" til et mysterium uden en hændelse at pege på. Tilbagekald og udsted — to rækker,\nto audit-poster, én tydelig historie.\n\nHash-cachen tømmes på vej ud. Uden det ville en tilbagekaldt nøgle blive ved med at\nautentificere indtil cachen tilfældigvis missede — forskellen mellem \"tilbagekaldt\" og\n\"tilbagekaldt om lidt\".","responses":{"200":{"description":"Tilbagekaldt.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhook_endpoints":{"get":{"tags":["Drift"],"operationId":"listWebhookEndpoints","summary":"Webhook-endpoints","description":"SPEC 4.9. Registreringen sker her; outboxen (SPEC 6.15) skriver allerede sine rækker i\nsamme transaktion som hver booking, så intet går tabt mellem \"bookingen skete\" og \"nogen\nfik det at vide\". Selve leveringsarbejderen er en senere milepæl —\n`delivery_enabled: false` siger det, frem for at lade en tom leveringsliste ligne en fejl.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Drift"],"operationId":"createWebhookEndpoint","summary":"Registrér webhook","description":"Hemmeligheden (`whsec_…`) genereres af os, én gang, og står i svaret: en modtager skal\nkunne verificere en `v1=`-signatur, og en signatur ingen kan tjekke er pynt.\n\n**Kun `https`, og ingen private værter.** En webhook er vores server der laver en\nforespørgsel på en fremmeds vegne; at pege den på `http://localhost:6379` er standardtrækket\ni en SSRF.\n\n`api_version` pinnes ved oprettelsen (SPEC 4.3): en payload-form en modtager parsede\ntirsdag skal stadig ankomme fredag.\n\nSPEC 6.15: skrivningen og dens audit-række deler én transaktion. \"Hvem satte kapaciteten ned\ntil 60\" bliver spurgt om i uge to.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointInput"}}}},"responses":{"200":{"description":"Fandtes allerede via `external_ref` (SPEC 4.8).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"201":{"description":"Oprettet.","headers":{"ETag":{"description":"Objektets `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhook_endpoints/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Drift"],"operationId":"getWebhookEndpoint","summary":"Webhook-endpoint","responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Objektets `updated_at` — tokenet PATCH kræver.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Drift"],"operationId":"updateWebhookEndpoint","summary":"Ret webhook — kræver If-Match","description":"`status: \"paused\"` er knappen et hus faktisk har brug for: modtagersystemet er ved at blive\ndeployet, og leveringerne skal købe frem for at brænde deres otte forsøg mod en 502.\n\nAt skifte URL beholder hemmeligheden. En modtager der er flyttet vært har ikke nødvendigvis\nroteret sin verifikationsnøgle, og en tvungen ny hemmelighed ville i stilhed knække hvert\nsignaturtjek på den anden side.\n\n**Optimistisk låsning uden `version`-kolonne.** SPEC 4.4 kræver `If-Match` på alt mutérbart;\nkatalogtabellerne har ingen `version` (SPEC 2.5 giver dem `updated_at` og intet andet), så\nsidste-skrivning-stemplet er tokenet: `ETag: \"<updated_at>\"`, og en PATCH med et forældet token\ner `412 version_conflict` med det aktuelle objekt vedhæftet — samme form som en booking svarer med.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","examples":["\"7\""]},"description":"Objektets `version` (SPEC 4.4, optimistisk låsning). Ved mismatch: 412 `version_conflict` med det aktuelle objekt vedhæftet."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointPatch"}}}},"responses":{"200":{"description":"Rettet.","headers":{"ETag":{"description":"Det nye `updated_at`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"Manglende obligatorisk header.\n\nMulige `code`-værdier: `idempotency_key_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Konflikt. Svaret indeholder `conflict` og — hvor det giver mening — `alternatives`. SPEC 4.7: \"Aldrig en blindgyde. En 409 skal altid pege videre.\"\n\nMulige `code`-værdier: `slot_unavailable`, `capacity_exceeded`, `requirement_unmet`, `outside_opening_hours`, `hold_expired`, `invalid_transition`, `idempotency_key_reused`, `request_in_flight`, `buffer_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Optimistisk låsning slog til. Det aktuelle objekt er vedhæftet som `current` (SPEC 4.4).\n\nMulige `code`-værdier: `version_conflict`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"428":{"description":"`If-Match` mangler på en PATCH.\n\nMulige `code`-værdier: `precondition_required`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Drift"],"operationId":"deleteWebhookEndpoint","summary":"Fjern webhook","description":"Fjerner registreringen og dens leveringsrækker. Et endpoint der er væk, er væk; audit-loggen bærer at det fandtes og hvor det pegede hen.","responses":{"204":{"description":"Fjernet."},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhook_endpoints/{id}/deliveries":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Drift"],"operationId":"listWebhookDeliveries","summary":"Seneste leveringer","description":"SPEC 4.9. Det eneste sted der besvarer \"fik de den?\", og forskellen på en integration et hus\nkan fejlsøge og en hvor svaret er en supportsag. Rækkerne joiner `outbox_event`, så hvert\nforsøg bærer HVAD der blev sendt.\n\nListen er tom indtil leveringsarbejderen findes — ærligt tom: `delivery_enabled: false` plus\n`pending_events`, antallet der er skrevet og venter.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhook_endpoints/{id}/rotate":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Drift"],"operationId":"rotateWebhookSecret","summary":"Rotér hemmeligheden","description":"SPEC 4.2's nøglemønster (`{\"expire_old_in\":\"24h\"}`) og SPEC 4.9's \"flere `v1=`-værdier\nunder hemmelighedsrotation\" er den samme mekanik: **rotation uden nedetid.**\n\nDen gamle hemmelighed bliver ved med at signere indtil vinduet lukker. I overlappet bærer\nhvert kald to `v1=`-værdier — én pr. hemmelighed — så jeres modtager kan skifte over\npræcis når det passer jer og er korrekt både før og efter. Et skift i én skrivning ville\nknække hvert signaturtjek på jeres side i vinduet mellem vores skrivning og jeres deploy,\nog det vindue kan ingen gøre til nul: det er to organisationer.\n\nVinduet er højst 30 dage. En gammel hemmelighed der virker længere end det, er ikke en\nrotation — det er to hemmeligheder.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookRotateInput"}}}},"responses":{"200":{"description":"Roteret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhook_endpoints/{id}/ping":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Drift"],"operationId":"pingWebhookEndpoint","summary":"Send et ping","description":"SPEC 4.9's `ping` og SPEC 4.10: *\"ingen ngrok, ingen offentlig URL, ingen deploy for at\nteste et webhook.\"* Hele pointen med onboardingen er at man kan finde ud af om modtageren\nvirker FØR der er noget at modtage.\n\nLeveres inde i kaldet, ikke ved arbejderens næste tik: svaret bærer det rigtige forsøg —\nstatuskode, varighed, og jeres egen fejltekst hvis der var en. En testknap hvis svar først\nkommer ti sekunder senere, er en testknap folk trykker på to gange og så holder op med at\nstole på.\n\nEt endpoint på pause køer pinget i stedet for at sende det. Det står i svaret.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"responses":{"202":{"description":"Sendt (eller sat i kø).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPingResult"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/events":{"get":{"tags":["Drift"],"operationId":"listEvents","summary":"Hændelser — redningsvejen","description":"SPEC 4.1 #6: *\"At-least-once webhooks, signeret, med replay. **Ingen webhook-arkitektur\nuden `GET /events` som redningsvej.**\"*\n\nDet er den her. En modtager der lå ned mellem 13 og 16, behøver ikke ringe: den læser sin\nsidste `sequence`, beder om alt efter den, og er ajour. Uden det endpoint bliver hver\nnedetid til en supportsag hvor nogen afspiller hændelser i hånden og gætter på grænsen.\n\n`type` taler samme sprog som et abonnement — `*`, `booking.*` eller den præcise type.\n`since` er RFC 3339 med offset; `since_sequence` er den en maskine bør bruge: SPEC 4.9\nsiger at der ingen rækkefølgegaranti er og peger på `sequence`, så at genoptage på et\nsekvensnummer er præcist, hvor et tidsstempel er et gæt om ure.\n\nSorteret STIGENDE: den der henter ind, vil have den ældste først. Hændelser gemmes i 30 dage.","parameters":[{"name":"type","in":"query","schema":{"type":"string"},"examples":{"alle":{"value":"booking.*"}}},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"},"description":"RFC 3339 med offset."},{"name":"since_sequence","in":"query","schema":{"type":"integer","minimum":0}},{"name":"starting_after","in":"query","schema":{"type":"string"},"description":"Cursor: et `evt_…`."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventList"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/events/{id}/replay":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Drift"],"operationId":"replayEvent","summary":"Send hændelsen igen","description":"SPEC 4.4. Knappen ved siden af en fejlet levering, og kaldet en integrator laver efter at\nhave rettet den fejl der fik deres handler til at kaste.\n\n**Den frosne konvolut sendes igen — ikke en frisk.** SPEC 4.9: *\"Et event afspejler\ntilstanden på leveringstidspunktet for det event.\"* En replay der genbyggede payloaden ud\nfra bookingen som den ser ud i dag, ville være en ANDEN hændelse med den gamles id, og en\nmodtager der anvender dem i rækkefølge, ville ende i en tilstand der aldrig har eksisteret.\n\n`endpoint_id` tilsidesætter abonnementet: at sende én hændelse til én navngiven modtager er\nen fejlsøgningshandling, og mennesket har allerede besluttet sig.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventReplayInput"}}}},"responses":{"202":{"description":"Sat i kø (og sendt, medmindre `deliver_now: false`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventReplayResult"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Objektet findes ikke i denne organisation.\n\nMulige `code`-værdier: `not_found`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/test/clock":{"get":{"tags":["Drift"],"operationId":"getTestClock","summary":"Hvad er klokken i min sandkasse?","description":"SPEC 4.1 #10, SPEC 4.4. **Kun testnøgler** (`lsk_test_…`) — en live-nøgle får 403.\n\nSvaret har både `now` (sandkassens ur) og `wall_clock_now` (det rigtige), så forskellen\nkan ses uden at regne. Den der har glemt at nulstille, fejlsøger ellers i en uge på en\nbooking der «altid ligger i fortiden».","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestClock"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Drift"],"operationId":"advanceTestClock","summary":"Ryk sandkassens ur frem","description":"SPEC 4.1 #10, ordret: *\"Sandbox med tidsmaskine. Man skal kunne teste «option udløber om\n14 dage» på 10 sekunder.\"* — og SPEC 4.10s minut 7: *\"udløser `option_expiring`,\n`option_expired` og påmindelser med det samme.\"*\n\n**Uret rejser i etaper, ikke i ét hop.** Springer man 15 dage frem og kører jobbene én\ngang, står optionen allerede som udløbet, og varslet to dage før den nåede aldrig at være\nforfaldent: man ville få `option_expired` uden `option_expiring`. Derfor standser kaldet\nved hvert forfaldstidspunkt undervejs — hver `option_expires_at`, `guarantee_due_at`,\n`deposit_due_at`, hver reservations udløb — og ved varslet om hvert af dem. Resultatet er\ndét man ville have set hvis man havde ventet i femten dage. `ticks` fortæller hvor mange\netaper der blev til.\n\n`advance` er tal + enhed: `15d`, `36h`, `90m`, `30s`, `2w`. Negativt ruller tilbage —\nmen kører ingen job: man kan ikke afsende en hændelse i går.\n\n**Kun testnøgler.** Uret kan ikke stilles på husets rigtige data.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"SPEC 4.8. Gemmes 24 timer med hash af body og det producerede svar. Samme nøgle + samme body afspiller svaret med `Idempotent-Replay: true`. Samme nøgle + anden body giver 409 `idempotency_key_reused`. Nøgle i brug giver 409 `request_in_flight`. 5xx gemmes ikke."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestClockAdvance"}}}},"responses":{"200":{"description":"Uret er rykket, og jobbene har kørt undervejs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestClock"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Forespørgslen kunne ikke valideres.\n\nMulige `code`-værdier: `validation_failed`, `min_notice_violated`, `horizon_exceeded`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Drift"],"operationId":"resetTestClock","summary":"Nulstil sandkassens ur","description":"SPEC 4.4. Uret følger vægur igen. **Sandkassens data røres ikke** — at rydde sandkassen\ner en anden beslutning end at holde op med at lyve om hvad klokken er, og de to må ikke\nsidde på den samme knap.","responses":{"200":{"description":"Uret er nulstillet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestClock"}}}},"401":{"description":"Ugyldig eller manglende nøgle.\n\nMulige `code`-værdier: `unauthorized`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Nøglen mangler det nødvendige scope.\n\nMulige `code`-værdier: `forbidden_scope`.","headers":{"X-Request-Id":{"description":"Returneres på ALLE svar, også succes (SPEC 4.2).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"SPEC 4.2. `Authorization: Bearer lsk_live_…` — aldrig nøgler i query-strengen.\n\nNøgletyper: `lsk_live_`/`lsk_test_` (server, alle scopes) og `lspk_live_`\n(browser/widget: `availability:read`, `catalog:read`, `holds:write`, `bookings:create`,\nkun offentlige ydelser, med origin-allowlist). Publishable-nøgler ser altid kalenderen\nmed offentlige øjne — en option blokerer dem.\n\nNøglen vises én gang; vi gemmer `sha256` og eksponerer `last_four` og `last_used_at`.\nOrganisationen udledes af nøglen — den nævnes aldrig i en forespørgsel.\n\n**Tredje legitimation: gæstelinket** (`lsbt_…`, SPEC 6.11). Et signeret HMAC-token der\nstår i kvitteringsmailen og er scopet til ÉN booking: den kan læses, flyttes og aflyses,\nog alt andet findes ikke (404). Det er samme route handlers som alle andre kalder."}},"schemas":{"Error":{"type":"object","description":"SPEC 4.7, RFC 9457-inspireret og ens overalt. En 409 peger altid videre via `alternatives`.","required":["type","code","message","request_id"],"properties":{"type":{"type":"string","format":"uri","examples":["https://docs.lavetsystem.dk/errors/slot_unavailable"]},"code":{"type":"string","enum":["unauthorized","forbidden_scope","not_found","validation_failed","capacity_exceeded","requirement_unmet","outside_opening_hours","min_notice_violated","horizon_exceeded","slot_unavailable","hold_expired","version_conflict","idempotency_key_reused","request_in_flight","rate_limited","payment_failed","internal_error","invalid_transition","idempotency_key_required","precondition_required","deadline_passed","buffer_conflict","too_many_holds","tenant_suspended"]},"message":{"type":"string","description":"Dansk, kundevendt, siger hvad man gør nu."},"request_id":{"type":"string","examples":["req_01JZQ8XN0000000000000000"]},"conflict":{"type":"object","description":"Hvem der er i vejen.","additionalProperties":true},"conflicts":{"type":"array","items":{"type":"object","additionalProperties":true}},"reasons":{"type":"array","items":{"$ref":"#/components/schemas/Reason"}},"alternatives":{"type":"array","description":"De nærmeste ledige tidspunkter. SPEC 4.7: aldrig en blindgyde.","items":{"type":"object","properties":{"start":{"type":"string","format":"date-time"},"resource_ids":{"type":"array","items":{"type":"string"}}}}},"errors":{"type":"array","items":{"type":"object","additionalProperties":true}},"current":{"$ref":"#/components/schemas/Booking"}}},"Reason":{"type":"object","description":"SPEC 4.5. Hvorfor et slot ikke kan bookes — i almindeligt dansk.","required":["code","detail"],"properties":{"code":{"type":"string","enum":["requirement_unmet","buffer_conflict","capacity_exceeded","outside_opening_hours","min_notice_violated","horizon_exceeded","pax_out_of_range","setup_capacity_exceeded","resource_unavailable","offering_not_bookable","no_candidate_resources","duration_invalid","overbooking_tolerance","result_truncated"]},"requirement_key":{"type":"string","examples":["room"]},"resource_id":{"type":"string"},"blocking_booking_id":{"type":"string"},"overridable":{"type":"boolean","description":"SPEC §6.2 [S]: sandt når det samme kald ville gå igennem fra admin-kanalen med `override_reason`. `buffer_conflict` er det altid; `capacity_exceeded` kun når ressourcens `overbooking_pct` faktisk dækker det der mangler."},"detail":{"type":"string","examples":["Sal A optaget af MCH-2026-0002; Sal B optaget af MCH-2026-0002"]}}},"Ping":{"type":"object","required":["object","ok","api_version"],"properties":{"object":{"const":"ping"},"ok":{"type":"boolean"},"api_version":{"type":"string"},"tenant":{"type":["object","null"],"additionalProperties":true},"key":{"type":"object","additionalProperties":true},"server_time":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Serverens ur. Alle regler evalueres mod dette, aldrig mod klientens (SPEC 6.3)."}}},"EmbedBootstrap":{"type":"object","description":"Widget’ens opstart (SPEC §7 fase 2). Samlingen af husets navn, katalog, deltagerkategorier, offentlige spørgsmål og ordforråd — hentet med widget’ens egen nøgle.","properties":{"object":{"const":"embed_bootstrap"},"house":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"timezone":{"type":"string","examples":["Europe/Copenhagen"]},"currency":{"type":"string","examples":["DKK"]}}},"page":{"type":"object","description":"Husets bookingside-indstillinger, som widget’en tegner sig efter.","additionalProperties":true},"links":{"type":"object","description":"Kun de veje der er tændt (SPEC §3 lag 5): `waitlist_url` er `null` når modulet er slået fra.","properties":{"inquiry_url":{"type":"string"},"waitlist_url":{"type":["string","null"]}}},"services":{"type":"array","items":{"$ref":"#/components/schemas/Offering"}},"addons":{"type":"array","items":{"$ref":"#/components/schemas/Offering"}},"categories":{"type":"array","items":{"$ref":"#/components/schemas/ParticipantCategory"}},"questions":{"type":"array","items":{"$ref":"#/components/schemas/FieldDef"}},"pricing_on":{"type":"boolean"},"terms":{"anyOf":[{"$ref":"#/components/schemas/Terms"},{"type":"null"}]},"blueprint_key":{"type":["string","null"]}}},"Availability":{"type":"object","required":["object","offering_id","timezone","channel","days","slots"],"properties":{"object":{"const":"availability"},"offering_id":{"type":"string"},"timezone":{"type":"string","examples":["Europe/Copenhagen"]},"channel":{"type":"string","enum":["public","admin"]},"days":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","examples":["2026-09-07"]},"status":{"type":"string","enum":["available","unavailable"]},"slot_count":{"type":"integer"},"reason_code":{"type":"string"},"price_from":{"type":"object","description":"Dagens billigste slot. Kun med `include=price` (SPEC 4.5).","properties":{"amount_minor":{"type":"integer","description":"Fra-prisen. I MINDSTE ENHED (øre). Aldrig float (SPEC 6.1 aksiom 6)."},"currency":{"type":"string","examples":["DKK"]}}}}}},"slots":{"type":"array","items":{"$ref":"#/components/schemas/Slot"}},"reasons":{"type":"array","items":{"$ref":"#/components/schemas/Reason"}},"has_more":{"type":"boolean","description":"Sandt når vinduet blev afkortet af `limit`. `days` og `slots` indeholder altid HELE lokale dage, så en afkortet dag med et forkert `slot_count` kan ikke opstå."},"next_cursor":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Første tidspunkt der IKKE er besvaret. Send det som `from` i næste kald."}}},"Slot":{"type":"object","required":["start","end","status"],"properties":{"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Gæstevendt start."},"end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Gæstevendt slut."},"status":{"type":"string","enum":["available","unavailable"]},"occupies":{"$ref":"#/components/schemas/Occupies"},"capacity_remaining":{"type":["integer","null"],"description":"Mindste resterende SHARED-kapacitet på tværs af kravene, fx kuverter i køkkenet."},"price":{"type":"object","description":"Kun med `include=price`, og kun for de første 120 ledige slots (SPEC 4.5). Udelades helt når ingen regel sætter en pris — et 0 ville blive tegnet som «gratis».","properties":{"currency":{"type":"string","examples":["DKK"]},"total_minor":{"type":"integer","description":"Slottets pris. I MINDSTE ENHED (øre). Aldrig float (SPEC 6.1 aksiom 6)."},"subtotal_minor":{"type":"integer","description":"Før moms. I MINDSTE ENHED (øre). Aldrig float (SPEC 6.1 aksiom 6)."},"vat_minor":{"type":"integer","description":"Momsbeløbet. I MINDSTE ENHED (øre). Aldrig float (SPEC 6.1 aksiom 6)."},"lines":{"type":"array","description":"Regelkæden, i den rækkefølge den blev anvendt (SPEC 4.1 #3).","items":{"type":"object","properties":{"label":{"type":"string"},"amount_minor":{"type":"integer","description":"Linjens bidrag. I MINDSTE ENHED (øre). Aldrig float (SPEC 6.1 aksiom 6)."},"rule_id":{"type":["string","null"]}}}}}},"assignments":{"type":"array","description":"Ét element pr. krav i ydelsen.","items":{"type":"object","properties":{"key":{"type":"string","examples":["room"]},"mode":{"type":"string","enum":["EXCLUSIVE","SHARED"]},"optional":{"type":"boolean"},"units":{"type":"integer","description":"Forbrug af `resource.capacity`."},"candidates":{"type":"array","items":{"type":"string"},"description":"Alt der kunne opfylde kravet."},"preferred":{"type":["string","null"],"description":"Hvad motoren valgte."},"resource_ids":{"type":"array","items":{"type":"string"}},"setup_id":{"type":["string","null"]},"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Segmentets start."},"end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Segmentets slut."}}}},"reasons":{"type":"array","items":{"$ref":"#/components/schemas/Reason"}}}},"Occupies":{"type":"object","description":"Det SPÆRREDE interval inkl. buffer og omstillingstid (SPEC 4.6, 6.7). Gæsten ser `start`/`end`; kalenderen og konfliktmotoren regner på dette.","properties":{"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":""},"end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":""}}},"SlotHoldCreate":{"type":"object","required":["offering_id","start"],"properties":{"offering_id":{"type":"string"},"start":{"type":"string","format":"date-time"},"pax":{"type":"integer","minimum":0},"participants":{"type":"object","additionalProperties":{"type":"integer"}},"setup_id":{"type":["string","null"]},"duration_minutes":{"type":"integer","minimum":1},"session_key":{"type":"string","description":"SPEC 6.5/6.15: loftet på aktive hold er pr. session — ellers kan en bot spærre et hus."},"ttl_minutes":{"type":"integer","minimum":1,"description":"Default: ydelsens `hold_ttl_min` (10 min)."},"preferred":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}},"SlotHold":{"type":"object","properties":{"id":{"type":"string","examples":["hld_01JZQ7X4M2000000000000000"]},"object":{"const":"slot_hold"},"offering_id":{"type":"string"},"session_key":{"type":"string"},"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":""},"end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":""},"expires_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Udløb. Filtreres på i HVER forespørgsel, ikke kun af oprydningsjobbet."},"extended_count":{"type":"integer"},"participant_total":{"type":"integer"},"released":{"type":"boolean"},"active":{"type":"boolean"},"resources":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"BookingCreate":{"type":"object","required":["offering_id","start"],"properties":{"offering_id":{"type":"string"},"start":{"type":"string","format":"date-time"},"pax":{"type":"integer","minimum":0},"participants":{"type":"object","additionalProperties":{"type":"integer"},"description":"Deltagerkategori → antal. Både kategoriens `key` (`adult`) og dens `id` (`pca_…`) accepteres — det er samme felt og samme betydning som på `/availability` og `/quotes`."},"status":{"type":"string","enum":["inquiry","quoted","option","confirmed"],"default":"confirmed","description":"Statusser en booking må OPRETTES med (SPEC 6.8). `option` dækker F&B-flowet \"læg en 1. option på Sal A\" uden en forespørgselsrække først. `inquiry` og `quoted` HOLDER intet (`block_level = 0`) og accepteres derfor også når tiden er optaget — det er hele pointen med event-sporet (SPEC 6.13). En forespørgsel får ingen allokeringsrækker."},"notify":{"type":"boolean","description":"Send bekræftelsen til gæsten. Standard: `true` for en publishable nøgle (gæsten har selv trykket Book), ellers `false` — SPEC 5.3: \"systemet sender aldrig en mail bag ryggen på nogen\"."},"setup_id":{"type":["string","null"]},"duration_minutes":{"type":"integer","minimum":1},"preferred":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}},"hold_id":{"type":["string","null"],"description":"Forbrug dette hold: dets allokeringer ignoreres i valideringen og frigives derefter."},"customer":{"type":"object","additionalProperties":true},"contact_name":{"type":["string","null"]},"contact_email":{"type":["string","null"]},"contact_phone":{"type":["string","null"]},"internal_note":{"type":["string","null"]},"customer_note":{"type":["string","null"]},"external_ref":{"type":["string","null"],"description":"Unik pr. organisation for altid. Andet idempotenslag (SPEC 4.8)."},"option_expires_at":{"type":["string","null"],"format":"date-time"},"hold_rank":{"type":["integer","null"],"description":"1. option, 2. option … (SPEC 6.8)."},"source":{"type":"string","enum":["public","admin","api","import"]},"override_reason":{"type":["string","null"],"description":"SPEC 6.2: admin må tilsidesætte en BLØD regel; begrundelsen logges ordret.\n\nKonkret i dag: en bufferkonflikt (SPEC 6.7 [S]) og åbningstiden. Får du\n`409 buffer_conflict` med `overridable: true`, er dette feltet du sender kaldet igen\nmed — begrundelsen ender i `audit_log` og som `booking.buffer_override` i bookingens\negen historik.\n\nKun `channel=admin`. Et overlap på selve gæsteintervallet er [H] og kan ikke\noverstyres af nogen."},"custom":{"type":["object","null"],"description":"Kundens definerede felter, valideret, synlige i UI."},"metadata":{"type":["object","null"],"description":"Opak integrator-taske. Valideres aldrig, vises aldrig."},"addons":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/AddonLine"},"description":"Forplejning og gebyrer, prissat og skrevet i SAMME transaktion som bookingen."}}},"AddonLine":{"type":"object","required":["offering_id"],"description":"En linje *Forplejning* (SPEC 5.3, 6.13). Tilvalget er en `offering` med\n`kind=\"addon\"` eller `\"fee\"` (SPEC 0 #6: én tabel).\n\nPrisen er **antal × enhedspris**: reglerne spørges om hvad ÉT stykke koster i\ntidsrummet — samme `explain()` som `/quotes` og tidsknapperne — og linjen ganger op.\nEn frokost skrevet som \"195 kr pr. deltager\" er 195 pr. kuvert; en flipover skrevet\nsom \"300 kr\" er 300 pr. stk.\n\nEt tilvalg med et eget KRAV afvises (422): det ville skulle lægge beslag på en\nressource, og segmenter er fase 2 (SPEC 0 #13). Book det som en selvstændig ydelse.","properties":{"offering_id":{"type":"string"},"quantity":{"type":"integer","minimum":1,"default":1,"description":"Kuverter, stk., portioner."},"allergy":{"type":["string","null"],"description":"SPEC 5.3s allergimarkering: \"3 vegetarer, 1 nøddeallergi\". Står på LINJEN, fordi det er sådan køkkenet læser den på køreplanen (SPEC 6.13)."},"note":{"type":["string","null"]}}},"BookingPatch":{"type":"object","description":"Kun felter der ikke flytter tid, ressourcer eller tilstand.","properties":{"contact_name":{"type":["string","null"]},"contact_email":{"type":["string","null"]},"contact_phone":{"type":["string","null"]},"customer_id":{"type":["string","null"]},"internal_note":{"type":["string","null"]},"customer_note":{"type":["string","null"]},"external_ref":{"type":["string","null"]},"guaranteed_count":{"type":["integer","null"],"description":"SPEC 6.13: garantitallet."},"addons":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/AddonLine"},"description":"Den KOMPLETTE liste af tilvalg (samme form som `requirements` på en ydelse): linjer der stadig står bliver, nye oprettes, manglende fjernes — sammen med deres fakturalinjer. En tom liste tager forplejningen af igen."},"custom":{"type":["object","null"]},"metadata":{"type":["object","null"]},"reason":{"type":["string","null"],"description":"Havner i audit-loggen."}}},"Booking":{"type":"object","required":["id","object","version","status","reference","timezone","start","end"],"description":"SPEC 4.6.","properties":{"id":{"type":"string","examples":["bkg_01JZQ7X4M2000000000000000"]},"object":{"const":"booking"},"version":{"type":"integer","description":"Send som `If-Match` ved PATCH (SPEC 4.4)."},"status":{"type":"string","enum":["inquiry","quoted","option","confirmed","completed","cancelled","expired","declined","no_show","late_released"]},"reference":{"type":"string","examples":["MCH-2026-0418"]},"option_expires_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Kun ved `status = option`."},"hold_rank":{"type":["integer","null"]},"challenge_due_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Frist for indehaveren af en udfordret option (SPEC 6.8)."},"timezone":{"type":"string"},"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Det der står i bekræftelsen."},"end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":""},"occupies":{"$ref":"#/components/schemas/Occupies"},"participants":{"type":"array","items":{"type":"object","additionalProperties":true}},"participant_total":{"type":"integer"},"resources":{"type":"array","description":"De allokeringer bookingen HOLDER nu (`block_level > 0`), med opstilling og det spærrede interval pr. ressource.","items":{"type":"object","additionalProperties":true}},"released_resources":{"type":"array","description":"Frigivne allokeringer (`block_level = 0`) fra en ombooking eller aflysning. De slettes aldrig (SPEC 2.3), men de spærrer intet og indgår ikke i `occupies`.","items":{"type":"object","additionalProperties":true}},"lines":{"type":"array","items":{"type":"object","additionalProperties":true}},"totals":{"type":"object","properties":{"currency":{"type":"string"},"subtotal_minor":{"type":"integer","description":"Netto. I MINDSTE ENHED (øre). Aldrig float (SPEC 6.1 aksiom 6)."},"vat_minor":{"type":"integer","description":"Moms. I MINDSTE ENHED (øre). Aldrig float (SPEC 6.1 aksiom 6)."},"total_minor":{"type":"integer","description":"Brutto. I MINDSTE ENHED (øre). Aldrig float (SPEC 6.1 aksiom 6)."},"paid_minor":{"type":"integer","description":"Sum af afregnede betalinger. I MINDSTE ENHED (øre). Aldrig float (SPEC 6.1 aksiom 6)."},"balance_due_minor":{"type":"integer","description":"Afledt, aldrig gemt (SPEC 2.5). Der findes ingen `paid`-boolean. I MINDSTE ENHED (øre). Aldrig float (SPEC 6.1 aksiom 6)."}}},"price_explanation":{"type":"array","description":"Tom indtil prismotoren (SPEC 6.12). Beløbene er 0 i M2.","items":{"type":"object","additionalProperties":true}},"guarantee":{"type":"object","additionalProperties":true},"beo":{"type":["object","null"],"description":"Køreplanens stand (SPEC 6.13), ikke arket selv — det hentes på `/bookings/{id}/documents`. `null` betyder at der aldrig er lavet en version, hvilket er en anden oplysning end \"version 0\".","properties":{"document_id":{"type":"string"},"version":{"type":"integer"},"sent_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Hvornår køkkenet fik den nyeste version."},"sent_to":{"type":["string","null"]},"changed_since_sent":{"type":"boolean"},"created_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"RFC 3339 med offset, i lokationens zone (SPEC 4.2, 6.10)."}}},"cancellation":{"type":"object","additionalProperties":true},"links":{"type":"object","description":"SPEC 4.6. Gæstens egne adresser, signeret med husets link-hemmelighed: `manage_url` (\"Min booking\" uden login), `receipt_url` og `ics_url`. Tokenet er en SIGNATUR over bookingens id — der er intet at holde synkront og intet at lække fra en tabel, og en roteret hemmelighed ugyldiggør hvert link vi nogensinde har sendt (SPEC 6.11).","properties":{"manage_url":{"type":"string"},"receipt_url":{"type":"string"},"ics_url":{"type":"string"}}},"message":{"type":"object","description":"Kun på svar hvor en besked blev forsøgt sendt: `{sent, to}` eller `{sent: false, reason}`.","additionalProperties":true},"customer_id":{"type":["string","null"]},"contact":{"type":"object","additionalProperties":true},"location_id":{"type":"string"},"source":{"type":"string"},"internal_note":{"type":["string","null"]},"customer_note":{"type":["string","null"]},"external_ref":{"type":["string","null"]},"custom":{"type":["object","null"]},"metadata":{"type":["object","null"]},"created_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":""},"updated_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":""}}},"Offering":{"type":"object","additionalProperties":true,"properties":{"object":{"const":"offering"}}},"Resource":{"type":"object","additionalProperties":true,"properties":{"object":{"const":"resource"}}},"ResourceType":{"type":"object","additionalProperties":true,"properties":{"object":{"const":"resource_type"}}},"Location":{"type":"object","additionalProperties":true,"properties":{"object":{"const":"location"}}},"ResourceInput":{"type":"object","required":["name"],"description":"SPEC 0 #1: ÉN kapacitetsakse. `capacity: 1` er et lokale; `capacity: 12` er en pulje på tolv. Om en booking tager det hele (EXCLUSIVE) eller en andel (SHARED) står på KRAVET, ikke her.","properties":{"name":{"type":"string","minLength":1},"location_id":{"type":"string","description":"Udelades: husets første lokation."},"resource_type_id":{"type":"string","description":"Påkrævet ved oprettelse."},"code":{"type":["string","null"]},"capacity":{"type":"integer","minimum":1,"default":1},"overbooking_pct":{"type":"integer","minimum":0,"maximum":50,"default":0,"description":"SPEC §7 fase 2: overbookingstolerance i procent af `capacity`, nedrundet til hele enheder. Gælder kun SHARED-forbrug og KUN admin-kanalen: et kald med `override_reason` må lægge sig i tolerancen, den offentlige flade kan ikke. Hver brug skriver `booking.overbooking_override` i `audit_log` og i bookingens historik. 0 (standard) betyder uændret [T]-kapacitet."},"turnaround_min":{"type":"integer","minimum":0,"description":"SPEC 6.7: lægges på BEGGE sider, max()-et mod opstillingens `changeover_min`."},"tags":{"type":"array","items":{"type":"string"},"description":"Puljevalg og prisregler."},"color":{"type":["string","null"]},"bookable_online":{"type":"boolean"},"schedule_id":{"type":["string","null"],"description":"Egen åbningstid (SPEC 6.9 lag 2)."},"component_resource_ids":{"type":"array","items":{"type":"string"},"description":"SPEC 2.4: delene i en sammensat ressource (\"Sal A+B\"). Sendes som HELE listen — den afledte `resource_conflict`-lukning genberegnes i samme transaktion, så en booking på Sal A fjerner Sal A+B fra kalenderen uden en eneste kodesti af sin egen."},"external_ref":{"type":["string","null"]},"payroll_external_id":{"type":["string","null"],"maxLength":64,"description":"SPEC §1.3 (løn-føre): medarbejderens nummer i LØNSYSTEMET — Danløn, Zenegy, Salary. Det er nøglen `GET /v1/shifts/export` skriver i kolonnen «lønnummer», og uden den kan en lønimport ikke matche de godkendte timer til et menneske. BEVIDST ikke det samme felt som `external_ref`: den peger på rækken i systemet ressourcen kom FRA og er LES Bookings egen idempotensnøgle (SPEC 4.8), så én CRM-import ville kunne overskrive et lønnummer hvis de delte felt. Tom streng gemmes som `null`."},"custom":{"type":["object","null"],"additionalProperties":true},"metadata":{"type":["object","null"],"additionalProperties":true}}},"Setup":{"type":"object","description":"SPEC 0 #2: `max_persons` validerer `pax` og allokeres ALDRIG. `resource.capacity` er hvad en booking forbruger; det her er hvor mange mennesker der er plads til, når lokalet står sådan.","properties":{"id":{"type":"string"},"object":{"const":"resource_setup"},"resource_id":{"type":"string"},"name":{"type":"string","examples":["Biograf","U-bord"]},"max_persons":{"type":"integer","minimum":1},"changeover_min":{"type":"integer","minimum":0},"sort_order":{"type":"integer"},"archived":{"type":"boolean"},"updated_at":{"type":"integer","description":"ETag-tokenet."}}},"SetupInput":{"type":"object","required":["name","max_persons"],"properties":{"name":{"type":"string","minLength":1},"max_persons":{"type":"integer","minimum":1},"changeover_min":{"type":"integer","minimum":0},"sort_order":{"type":"integer"}}},"ScheduleRule":{"type":"object","description":"SPEC 2.5 #10 / 6.9: minutter siden LOKAL midnat, og `close_min` MÅ overstige 1440 (baren der lukker 02:00 er 1560). Tekstklokkeslæt sorterer og sammenlignes forkert over midnat.","properties":{"id":{"type":"string"},"object":{"const":"schedule_rule"},"kind":{"type":"string","enum":["open","closed","online_only"]},"weekday":{"type":"integer","minimum":1,"maximum":7,"description":"ISO: 1 = mandag."},"open_min":{"type":"integer","minimum":0},"close_min":{"type":"integer","minimum":1,"maximum":2880},"valid_from":{"type":["string","null"],"examples":["2026-09-01"]},"valid_to":{"type":["string","null"]},"priority":{"type":"integer","description":"Højeste bånd vinder helt (SPEC 6.9)."}}},"ScheduleException":{"type":"object","description":"SPEC 6.9 lag 3. `closed` slår `special`, og den smalleste periode vinder.","properties":{"id":{"type":"string"},"object":{"const":"schedule_exception"},"date_from":{"type":"string","examples":["2026-08-03"]},"date_to":{"type":"string"},"kind":{"type":"string","enum":["closed","special"]},"open_min":{"type":["integer","null"]},"close_min":{"type":["integer","null"]},"note":{"type":["string","null"],"examples":["Ombygning"]},"updated_at":{"type":"integer"}}},"Schedule":{"type":"object","properties":{"id":{"type":"string"},"object":{"const":"schedule"},"name":{"type":"string"},"rules":{"type":"array","items":{"$ref":"#/components/schemas/ScheduleRule"}},"exceptions":{"type":"array","items":{"$ref":"#/components/schemas/ScheduleException"}},"used_by":{"type":"array","description":"Lokationer, ressourcer og ydelser der peger på skemaet.","items":{"type":"object","properties":{"object":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"}}}},"updated_at":{"type":"integer"}}},"ScheduleInput":{"type":"object","description":"`rules` ERSTATTER hele ugeskemaet. Et ugeskema er én ting for den der redigerer det; en delvis patch af syv ugedage er sådan en tirsdag stille beholder tider ingen har valgt.","properties":{"name":{"type":"string","minLength":1},"rules":{"type":"array","items":{"type":"object","required":["weekday","open_min","close_min"],"properties":{"kind":{"type":"string","enum":["open","closed","online_only"]},"weekday":{"type":"integer","minimum":1,"maximum":7},"open_min":{"type":"integer","minimum":0},"close_min":{"type":"integer","minimum":1,"maximum":2880},"valid_from":{"type":["string","null"]},"valid_to":{"type":["string","null"]},"priority":{"type":"integer"}}}}}},"ResourceSchedule":{"type":"object","description":"SPEC 6.9's lagdeling, gjort synlig: hvilket skema der GÆLDER, og om det er ressourcens eget eller husets. Uden `inherited` kan et UI ikke sige \"Sal A følger husets åbningstid\".","properties":{"object":{"const":"resource_schedule"},"resource_id":{"type":"string"},"inherited":{"type":"boolean"},"source":{"type":"string","enum":["resource","location","none"]},"location_schedule_id":{"type":["string","null"]},"schedule":{"anyOf":[{"$ref":"#/components/schemas/Schedule"},{"type":"null"}]}}},"Holiday":{"type":"object","description":"SPEC 6.9: **hardcode dem aldrig.** Store bededag blev afskaffet fra 2024, så enhver hardkodet liste er allerede forkert. Genereres pr. år og redigeres. Lukker aldrig noget af sig selv — et kongrescenter er ofte åbent 2. juledag.","properties":{"id":{"type":"string"},"object":{"const":"holiday"},"date":{"type":"string","examples":["2026-12-26"]},"name":{"type":"string"},"is_national":{"type":"boolean"},"year":{"type":"integer"},"updated_at":{"type":"integer"}}},"Requirement":{"type":"object","description":"SPEC 2.2: \"ressource + krav\" er hele modellen. `mode` bor HER og ikke på ressourcen — samme køkken deles af en buffet (SHARED) og lægges beslag på af en demoaften (EXCLUSIVE).","properties":{"id":{"type":"string"},"object":{"const":"offering_requirement"},"segment_id":{"type":"string"},"key":{"type":"string","examples":["room"],"description":"Navnet svaret og `reasons` bruger."},"resource_type_id":{"type":"string"},"pool":{"type":["object","null"],"properties":{"resource_ids":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"min_capacity":{"type":"integer"}}},"quantity":{"type":"number","description":"Fast behov."},"quantity_per_pax":{"type":"number","description":"Behov = ceil(quantity + per_pax × pax)."},"mode":{"type":"string","enum":["EXCLUSIVE","SHARED"]},"assignment":{"type":"string","enum":["manual","first_available","least_used","round_robin"]},"customer_selectable":{"type":"boolean"},"setup_selectable":{"type":"boolean"},"optional":{"type":"boolean"},"sort_order":{"type":"integer"}}},"OfferingInput":{"type":"object","required":["name"],"description":"SPEC 5.3 #4: seks felter giver en fungerende ydelse — navn, varighed, min/max deltagere, starttidsinterval og ét krav. Resten har defaults et dansk hus kan leve med.\n\n`requirements` er en KOMPLET liste: rækker med `id` opdateres, nye oprettes, og dem der mangler fjernes. Id'er genbruges bevidst — de optræder i `availability.assignments` og i de `reasons` en supportsamtale citerer.","properties":{"kind":{"type":"string","enum":["service","addon","package","fee"]},"name":{"type":"string","minLength":1},"slug":{"type":"string","description":"Udledes af navnet, hvis det udelades."},"description":{"type":["string","null"]},"location_id":{"type":["string","null"]},"duration_mode":{"type":"string","enum":["fixed","options","customer"]},"duration_minutes":{"type":["integer","null"],"minimum":1},"duration_options":{"type":["array","null"],"items":{"type":"integer"}},"buffer_before_min":{"type":"integer","minimum":0},"buffer_after_min":{"type":"integer","minimum":0},"slot_interval_min":{"type":"integer","minimum":1},"slot_offset_min":{"type":"integer","minimum":0,"description":"Skal være mindre end `slot_interval_min`."},"min_notice_min":{"type":"integer","minimum":0},"max_horizon_days":{"type":"integer","minimum":1},"min_pax":{"type":"integer","minimum":0},"max_pax":{"type":["integer","null"],"minimum":1},"confirmation_mode":{"type":"string","enum":["auto","manual"]},"option_hold_hours":{"type":["integer","null"]},"hold_ttl_min":{"type":"integer","minimum":1},"deposit_mode":{"type":"string","enum":["none","pct","amount"]},"deposit_value":{"type":["integer","null"]},"cancel_policy_id":{"type":["string","null"]},"guarantee_lead_workdays":{"type":["integer","null"],"description":"SPEC 6.13: HVERDAGE. Derfor er helligdagstabellen en forudsætning."},"guarantee_tolerance_pct":{"type":["integer","null"]},"bookable_online":{"type":"boolean"},"spans_days":{"type":"boolean"},"schedule_id":{"type":["string","null"]},"vat_rate_bp":{"type":"integer","description":"2500 = 25 %. Snapshottes pr. linje."},"external_ref":{"type":["string","null"]},"custom":{"type":["object","null"],"additionalProperties":true},"metadata":{"type":["object","null"],"additionalProperties":true},"requirements":{"type":"array","items":{"type":"object","required":["key","resource_type_id"],"properties":{"id":{"type":"string"},"key":{"type":"string","pattern":"^[a-z0-9_]+$"},"resource_type_id":{"type":"string"},"pool":{"type":["object","null"],"additionalProperties":true},"quantity":{"type":"number","minimum":0},"quantity_per_pax":{"type":"number","minimum":0},"mode":{"type":"string","enum":["EXCLUSIVE","SHARED"]},"assignment":{"type":"string"},"customer_selectable":{"type":"boolean"},"setup_selectable":{"type":"boolean"},"optional":{"type":"boolean"},"sort_order":{"type":"integer"}}}}}},"OfferingDetail":{"allOf":[{"$ref":"#/components/schemas/Offering"},{"type":"object","description":"Kun på item-endpointet. En liste med 200 ydelser må ikke blive til 200 ekstra forespørgsler, og SPEC 4.2 låser fase 1's `expand`-sæt til `customer,offering,resources`.","properties":{"segment_id":{"type":["string","null"],"description":"Fase 1: præcis ét segment (SPEC 0 #13)."},"requirements":{"type":"array","items":{"$ref":"#/components/schemas/Requirement"}},"updated_at":{"type":"integer"}}}]},"ResourceTypeInput":{"type":"object","required":["label_singular","label_plural"],"description":"SPEC 3 lag 3: hele fase 1's terminologi. \"lokale\" / \"bord\" / \"stol\" er en RÆKKE, aldrig en kodegren — og derfor skal den kunne skrives fra opsætningen.","properties":{"key":{"type":"string","pattern":"^[a-z0-9_]+$"},"label_singular":{"type":"string","minLength":1},"label_plural":{"type":"string","minLength":1},"icon":{"type":["string","null"]},"is_staff":{"type":"boolean"},"sort_order":{"type":"integer"}}},"LocationInput":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1},"timezone":{"type":"string","examples":["Europe/Copenhagen"],"description":"IANA, aldrig et fast offset (SPEC 6.10)."},"address":{"type":["string","null"]},"vat_no":{"type":["string","null"]},"schedule_id":{"type":["string","null"]},"external_ref":{"type":["string","null"]},"custom":{"type":["object","null"],"additionalProperties":true},"metadata":{"type":["object","null"],"additionalProperties":true}}},"Customer":{"type":"object","required":["id","object","kind","name"],"description":"SPEC 2.5 lag 5. Slettes aldrig — anonymiseres når der findes fakturalinjer.","properties":{"id":{"type":"string"},"object":{"const":"customer"},"kind":{"type":"string","enum":["person","company"]},"name":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"company_name":{"type":["string","null"]},"cvr":{"type":["string","null"],"description":"8 cifre, modulus-11 (SPEC 6.14)."},"ean":{"type":["string","null"],"description":"13 cifre med EAN-13-kontrolciffer (SPEC 6.14)."},"invoice_address":{"type":["string","null"]},"notes":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"}},"no_show_count":{"type":"integer","description":"SPEC §6.8: hele levetiden. Skrives af motoren når en booking sættes til `no_show`, aldrig gennem dette API."},"risk_flag":{"type":"boolean","description":"Kun læsning. Sættes og ophæves af huset egen regel (SPEC §7 fase 2)."},"no_show_flagged_at":{"type":["integer","null"],"description":"Epoch i ms: hvornår `risk_flag` blev sat AUTOMATISK af reglen om udeblivelser. `null` betyder at reglen ikke står bag. Kun den den selv har sat, ophæver den igen."},"anonymized":{"type":"boolean"},"archived":{"type":"boolean"},"updated_at":{"type":"integer","description":"Epoch i ms. Tokenet `If-Match` sammenlignes med — se PATCH."}}},"CustomerInput":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1},"kind":{"type":"string","enum":["person","company"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"company_name":{"type":["string","null"]},"cvr":{"type":["string","null"]},"ean":{"type":["string","null"]},"invoice_address":{"type":["string","null"]},"notes":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"}},"external_ref":{"type":["string","null"]},"custom":{"type":["object","null"],"additionalProperties":true},"metadata":{"type":["object","null"],"additionalProperties":true}}},"ParticipantCategory":{"type":"object","required":["id","object","key","label"],"properties":{"id":{"type":"string"},"object":{"const":"participant_category"},"key":{"type":"string","examples":["adult"]},"label":{"type":"string","examples":["Voksen"]},"min_age":{"type":["integer","null"]},"max_age":{"type":["integer","null"]},"counts_toward_capacity":{"type":"boolean"},"sort_order":{"type":"integer"},"archived":{"type":"boolean"}}},"FieldDef":{"type":"object","description":"SPEC 3 lag 2: kundens eget felt. Svaret lever i `booking.custom`.","properties":{"id":{"type":"string"},"object":{"const":"field_def"},"entity":{"type":"string","enum":["booking","booking_line","customer","resource","offering"]},"key":{"type":"string"},"label":{"type":"string"},"type":{"type":"string","examples":["text","textarea","select","multiselect","number","ean","date","boolean"]},"required":{"type":"boolean"},"visibility":{"type":"string","enum":["public","admin","both"]},"options":{"type":["array","null"],"items":{"type":"object","additionalProperties":true}},"scope_offering_id":{"type":["string","null"]},"in_beo":{"type":"boolean","description":"Om svaret trykkes på køreplanen (SPEC 6.13)."},"sort_order":{"type":"integer"}}},"BookingEvent":{"type":"object","description":"SPEC 2.5: append-only. Én række pr. skrivning.","properties":{"id":{"type":"string"},"object":{"const":"booking_event"},"booking_id":{"type":"string"},"type":{"type":"string","examples":["booking.rescheduled","booking.participants_changed"]},"from_status":{"type":["string","null"]},"to_status":{"type":["string","null"]},"actor":{"type":"object","properties":{"type":{"type":"string","enum":["user","customer","system","api"]},"id":{"type":["string","null"]},"name":{"type":["string","null"],"description":"Menneskets navn når et menneske gjorde det."}}},"payload":{"type":["object","null"],"additionalProperties":true},"reason":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":""}}},"Allocation":{"type":"object","description":"Én række i `resource_allocation` (SPEC 2.3) plus det bookingresumé en blok i tidslinjen skal kunne læses ud fra uden et kald mere.","properties":{"id":{"type":"string"},"object":{"const":"resource_allocation"},"resource_id":{"type":"string"},"resource_name":{"type":["string","null"]},"location_id":{"type":["string","null"]},"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Gæstevendt start — det der står i bekræftelsen."},"end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Gæstevendt slut."},"occupies":{"$ref":"#/components/schemas/Occupies"},"mode":{"type":"string","enum":["EXCLUSIVE","SHARED"]},"units":{"type":"integer","description":"Forbrug af `resource.capacity` (SPEC 6.6)."},"block_level":{"type":"integer","enum":[0,1,2],"description":"2 = hård · 1 = option (spærrer kun den offentlige flade) · 0 = frigivet."},"hold_rank":{"type":["integer","null"],"description":"1. option, 2. option … Flere optioner må overlappe (SPEC 6.8 [T])."},"expires_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Udløb på hold og optioner."},"kind":{"type":"string","enum":["booking","option","hold","block","maintenance","closed"]},"setup_id":{"type":["string","null"]},"setup_name":{"type":["string","null"]},"requirement_key":{"type":["string","null"],"description":"Hvilket krav denne ressource dækker. Allokeringsrækken gemmer ressourcer, ikke krav (SPEC 2.3) — nøglen udledes. Send den som `preferred` når du flytter en booking til en anden ressource."},"slot_hold_id":{"type":["string","null"]},"booking_line_id":{"type":["string","null"]},"booking":{"type":["object","null"],"description":"Null på rækker der ikke hører til en booking (lukkedage, vedligehold).","properties":{"id":{"type":"string"},"reference":{"type":["string","null"]},"status":{"type":["string","null"]},"title":{"type":["string","null"],"description":"Kunde, ellers kontaktperson, ellers reference."},"offering_name":{"type":["string","null"]},"participant_total":{"type":["integer","null"]},"option_expires_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Hvornår optionen falder væk."},"url":{"type":"string"}}}}},"AttentionItem":{"type":"object","description":"Én ting der kræver en beslutning. `kind` bestemmer hvad `due_at` betyder — og listen af `kind` er additiv (SPEC 4.1 #5): en ukendt værdi skal springes over, ikke kastes på.","properties":{"id":{"type":"string","description":"Stabil inden for ét svar: `kind:booking_id`."},"object":{"const":"attention_item"},"kind":{"type":"string","enum":["option_expiring","option_challenged","inquiry_unanswered","menu_missing","guarantee_due","invoice_overdue"]},"booking_id":{"type":"string"},"reference":{"type":"string"},"status":{"type":"string"},"customer_name":{"type":["string","null"]},"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Arrangementets start — det fristen handler om."},"end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Arrangementets slut."},"due_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Det øjeblik punktet måles mod: optionens udløb, garantifristen, fakturaens forfald — eller, for en forespørgsel, hvornår tålmodigheden er brugt op."},"overdue":{"type":"boolean","description":"Fristen er passeret. Ikke \"snart\" — forbi."},"participant_total":{"type":"integer"},"hold_rank":{"type":["integer","null"]},"invoice_id":{"type":["string","null"],"description":"Kun på `invoice_overdue`."},"invoice_number":{"type":["string","null"]},"amount_due_minor":{"type":["integer","null"],"description":"Afledt på serveren (SPEC 2.5) — aldrig genberegnet i en klient."},"currency":{"type":["string","null"]},"url":{"type":"string"}}},"AttentionEntry":{"type":"object","description":"Én linje i dagens program: tid, lokale, opstilling, kuverttal og allergier — det køkkenet og opsætningen skal kunne læse på ét blik (SPEC 5.3 skærm 1).","properties":{"booking_id":{"type":"string"},"object":{"const":"attention_entry"},"reference":{"type":"string"},"status":{"type":"string"},"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Gæstevendt start."},"end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Gæstevendt slut."},"customer_name":{"type":["string","null"]},"offering_name":{"type":["string","null"]},"participant_total":{"type":"integer"},"guaranteed_count":{"type":["integer","null"],"description":"Det låste garantital (SPEC 6.13). Er det sat, er det DET køkkenet laver mad til — og det tal skærmen viser i stedet for det bestilte."},"hold_rank":{"type":["integer","null"]},"resources":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"setup":{"type":["string","null"]}}}},"allergies":{"type":"array","items":{"type":"string"},"description":"Husets egne ord, pr. linje. Allergien sidder på LINJEN, fordi det er sådan køkkenet læser den — \"nøddeallergi\" hører til buffeten, ikke til lokalet."},"url":{"type":"string"}}},"Attention":{"type":"object","description":"Hele forsiden i ét svar (SPEC 5.3 skærm 1).","properties":{"object":{"const":"attention"},"date":{"type":"string","examples":["2026-07-25"],"description":"Den lokale dag svaret handler om."},"timezone":{"type":"string","examples":["Europe/Copenhagen"]},"generated_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Serverens ur, da svaret blev dannet."},"items":{"type":"array","items":{"$ref":"#/components/schemas/AttentionItem"}},"today":{"type":"array","items":{"$ref":"#/components/schemas/AttentionEntry"}},"tomorrow":{"type":"array","items":{"$ref":"#/components/schemas/AttentionEntry"}},"yesterday":{"type":"array","items":{"$ref":"#/components/schemas/AttentionEntry"}}}},"BeoSnapshot":{"type":"object","description":"Køreplanens indhold, frosset ved genereringen (SPEC 6.13). Læses den et år senere, siger den hvad køkkenet FIK at vide — ikke hvad bookingen ser ud som nu.","properties":{"object":{"const":"beo"},"booking_id":{"type":"string"},"reference":{"type":"string"},"status":{"type":"string"},"timezone":{"type":"string","examples":["Europe/Copenhagen"]},"event":{"type":"object","properties":{"title":{"type":"string"},"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Arrangementets start."},"end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Arrangementets slut."},"location":{"type":["string","null"]},"customer":{"type":["string","null"]}}},"contact":{"type":"object","properties":{"name":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]}}},"schedule":{"type":"array","description":"Tider pr. linje/segment.","items":{"type":"object","properties":{"label":{"type":"string"},"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"RFC 3339 med offset, i lokationens zone (SPEC 4.2, 6.10)."},"end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"RFC 3339 med offset, i lokationens zone (SPEC 4.2, 6.10)."},"quantity":{"type":"integer"},"pax":{"type":"integer"},"room":{"type":["string","null"]},"setup":{"type":["string","null"]},"status":{"type":"string"}}}},"rooms":{"type":"array","items":{"type":"object","properties":{"resource":{"type":"string"},"setup":{"type":["string","null"]},"max_persons":{"type":["integer","null"]},"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"RFC 3339 med offset, i lokationens zone (SPEC 4.2, 6.10)."},"end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"RFC 3339 med offset, i lokationens zone (SPEC 4.2, 6.10)."},"occupies_start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Med buffer — hvad lokalet reelt er blokeret i."},"occupies_end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"RFC 3339 med offset, i lokationens zone (SPEC 4.2, 6.10)."}}}},"covers":{"type":"object","description":"Kuverttal, i alt og pr. deltagerkategori.","properties":{"total":{"type":"integer"},"by_category":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"count":{"type":"integer"}}}}}},"menu":{"type":"array","description":"Forplejningen med antal — SPEC 5.3 *Forplejning*.","items":{"type":"object","properties":{"label":{"type":"string"},"quantity":{"type":"integer"},"allergy":{"type":["string","null"]},"note":{"type":["string","null"]}}}},"allergies":{"type":"array","items":{"type":"string"},"description":"Samlet fra tilvalgenes allergimarkering og fra de `in_beo`-felter huset har kaldt noget med allergi. Fremhævet øverst på arket, aldrig gemt i en liste."},"notes":{"type":"object","properties":{"kitchen":{"type":["string","null"]},"customer":{"type":["string","null"]}}},"guarantee":{"type":"object","properties":{"count":{"type":["integer","null"]},"due_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Garantifristen (SPEC 6.13 [T])."},"menu_due_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"RFC 3339 med offset, i lokationens zone (SPEC 4.2, 6.10)."},"tolerance_pct":{"type":["integer","null"]}}},"fields":{"type":"array","description":"Hvert `field_def` med `in_beo = true` (SPEC 3 lag 2).","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"value":{"type":"string"}}}}}},"BeoDiff":{"type":"object","description":"Hvad der er ændret siden forrige version, felt for felt — SPEC 6.13. `summary` er sætningen et menneske læser; `changes` er den samme information maskinlæsbar.","properties":{"from_version":{"type":"integer"},"to_version":{"type":"integer"},"summary":{"type":"string","examples":["Ændringer siden v3: kuverter 48→52, menu tilføjet Vegetar ×4"]},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","examples":["covers.total"]},"kind":{"type":"string","enum":["added","removed","changed"]},"from":{"type":["string","null"]},"to":{"type":["string","null"]},"text":{"type":"string","examples":["kuverter 48→52"]}}}}}},"QuoteSnapshot":{"type":"object","description":"Tilbuddets indhold, frosset ved genereringen (SPEC 6.13, BACKLOG #222). Linjerne er bookingens egne `invoice_line` — tilbuddet BEREGNER ikke en pris, det samler de rækker fakturaen senere stempler, så de to aldrig kan blive uenige om et beløb. `template` er husets egne tekster og felt-valg, som de stod da versionen blev lavet: retter huset sin indledning i marts, siger februars tilbud stadig februars ord (samme regel som `policy_snapshot`, SPEC 6.11).","properties":{"object":{"const":"quote"},"booking_id":{"type":"string"},"reference":{"type":"string"},"status":{"type":"string"},"timezone":{"type":"string","examples":["Europe/Copenhagen"]},"issued_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Da versionen blev lavet."},"valid_until":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Gyldighedsfristen — issued_at plus husets valid_days."},"house":{"type":"object","properties":{"name":{"type":"string"},"address":{"type":["string","null"]},"vat_no":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]}}},"event":{"type":"object","properties":{"title":{"type":"string"},"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"RFC 3339 med offset, i lokationens zone (SPEC 4.2, 6.10)."},"end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"RFC 3339 med offset, i lokationens zone (SPEC 4.2, 6.10)."},"location":{"type":["string","null"]},"customer":{"type":["string","null"]},"pax":{"type":["integer","null"]}}},"contact":{"type":"object","properties":{"name":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]}}},"lines":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string"},"qty_milli":{"type":"integer","description":"1000 = én enhed."},"unit_net_minor":{"type":"integer"},"vat_rate_bp":{"type":"integer","examples":[2500]},"net_minor":{"type":"integer"},"vat_minor":{"type":"integer"},"gross_minor":{"type":"integer"},"currency":{"type":"string","examples":["DKK"]}}}},"rooms":{"type":"array","items":{"type":"object","properties":{"resource":{"type":"string"},"setup":{"type":["string","null"]},"max_persons":{"type":["integer","null"]},"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"RFC 3339 med offset, i lokationens zone (SPEC 4.2, 6.10)."},"end":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"RFC 3339 med offset, i lokationens zone (SPEC 4.2, 6.10)."}}}},"totals":{"type":"object","properties":{"currency":{"type":"string"},"net_minor":{"type":"integer"},"vat_minor":{"type":"integer"},"gross_minor":{"type":"integer"},"vat_breakdown":{"type":"array","description":"Én række pr. momssats — erhvervskunden bogfører efter den.","items":{"type":"object","properties":{"vat_rate_bp":{"type":"integer"},"net_minor":{"type":"integer"},"vat_minor":{"type":"integer"},"gross_minor":{"type":"integer"}}}}}},"deposit":{"anyOf":[{"type":"object","properties":{"amount_minor":{"type":"integer"},"due_days":{"type":["integer","null"],"description":"Dage efter et ja, ikke en dato: SPEC 6.14 beregner den absolutte frist VED bekræftelsen, og et tilbud er per definition før den."}}},{"type":"null"}],"description":"Hvad depositummet VILLE blive — beregnet med den samme funktion som bekræftelsen bruger, så tallet på tilbuddet og tallet på bekræftelsen ikke kan blive to tal."},"terms":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"integer"},"tiers":{"type":"array","items":{"type":"object","properties":{"days_before":{"type":"integer"},"fee_pct":{"type":"integer"}}}},"cancel_until_hours":{"type":["integer","null"]}}},{"type":"null"}],"description":"Uddraget af afbestillingsvilkårene. Bookingens frosne politik hvis den er bekræftet, ellers ydelsens nuværende — for det er dén der ville gælde ved et ja i dag."},"template":{"$ref":"#/components/schemas/DocumentTemplate"}}},"QuoteDiff":{"type":"object","description":"Hvad der er anderledes siden forrige version, i husets egne ord: *\"Ændringer siden v1: pris 44.640,00 kr. → 48.900,00 kr., deltagere 48 → 52\"*.","properties":{"from_version":{"type":"integer"},"to_version":{"type":"integer"},"summary":{"type":"string"},"changes":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","examples":["totals.gross"]},"kind":{"type":"string","enum":["added","removed","changed"]},"from":{"type":["string","null"]},"to":{"type":["string","null"]},"text":{"type":"string"}}}}}},"DocumentTemplate":{"type":"object","description":"Husets egne ord og felt-valg for én dokumenttype (SPEC §7 fase 2, SPEC §3). **Ikke en template-motor:** der er ingen pladsholdere, ingen markup og ingen betingelser. Strukturen ejes af systemet; huset ejer fire tekstblokke, sit logo, sin farve og hvilke felter der er med. Formen er den samme som i `settings.documents.templates.<type>`, og et dokument bærer en KOPI af den, frosset ved genereringen.","properties":{"intro":{"type":["string","null"]},"outro":{"type":["string","null"]},"terms_text":{"type":["string","null"]},"payment_text":{"type":["string","null"]},"logo_url":{"type":["string","null"]},"accent":{"type":["string","null"],"examples":["#2f4f8f"]},"fields":{"type":"object","additionalProperties":{"type":"boolean"},"description":"Feltnøgle → med eller ikke med. Nøglerne er lukkede pr. dokumenttype; en ukendt nøgle i en PATCH svarer 422 (in_beo-mønstret fra SPEC §3 lag 2, ét lag op)."}}},"Document":{"type":"object","description":"En version af et bookingdokument (SPEC 2.5 `document`, SPEC 6.13) — enten køreplanen til køkkenet (`beo`) eller tilbuddet til kunden (`quote`). Samme versionsmekanik, samme diff, samme udsendelse; `content` er typens eget indhold.","properties":{"id":{"type":"string","examples":["doc_01JZQ7X4M2"]},"object":{"const":"document"},"type":{"type":"string","enum":["beo","quote"]},"booking_id":{"type":"string"},"version":{"type":"integer","minimum":1},"booking_version":{"type":"integer","description":"Bookingens `version` da arket blev lavet — til sporet, ikke til diffen."},"content":{"anyOf":[{"$ref":"#/components/schemas/BeoSnapshot"},{"$ref":"#/components/schemas/QuoteSnapshot"}]},"diff":{"anyOf":[{"$ref":"#/components/schemas/BeoDiff"},{"$ref":"#/components/schemas/QuoteDiff"},{"type":"null"}]},"sent_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Null indtil dokumentet er sendt."},"sent_to":{"type":["string","null"]},"changed_since_sent":{"type":"boolean","description":"SPEC 6.13: bookingen har flyttet sig siden den nyeste udsendte version. Altid `false` når intet er sendt — et ark der aldrig har forladt huset kan ikke være ændret siden udsendelse."},"created_at":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"RFC 3339 med offset, i lokationens zone (SPEC 4.2, 6.10)."},"message":{"type":"object","description":"Kun på `/send`: gik den ud, og hvis ikke, hvorfor.","properties":{"sent":{"type":"boolean"},"to":{"type":"string"},"reason":{"type":"string"}}}}},"SearchResult":{"type":"object","properties":{"object":{"const":"search_result"},"type":{"type":"string","enum":["booking","customer","offering","resource"]},"id":{"type":"string"},"label":{"type":"string"},"secondary":{"type":["string","null"]},"status":{"type":"string"},"start":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Kun på bookinger."},"resource_url":{"type":"string"}}},"AllocationList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Allocation"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"},"timezone":{"type":"string","examples":["Europe/Copenhagen"],"description":"Lokationens IANA-zone. Alle tidsstempler ovenfor er udtrykt i den."}}},"SearchResultList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/SearchResult"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"},"query":{"type":"object","description":"Hvordan linjen blev læst. `text` er det der blev søgt på i databasen, `date` den dato der blev læst ud af linjen (eller null), `date_text` præcis de tegn der blev læst som dato.","properties":{"raw":{"type":"string"},"text":{"type":"string"},"date":{"type":["string","null"],"examples":["2026-10-03"]},"date_text":{"type":["string","null"],"examples":["3/10"]},"timezone":{"type":"string","examples":["Europe/Copenhagen"]}}}}},"BookingList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Booking"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"BookingEventList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/BookingEvent"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"DocumentList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Document"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"},"preview":{"$ref":"#/components/schemas/BeoSnapshot","description":"Hvad en ny version ville indeholde lige nu."},"changed_since_sent":{"type":"boolean"}}},"CustomerList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Customer"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"ParticipantCategoryList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ParticipantCategory"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"FieldDefList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/FieldDef"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"OfferingList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Offering"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"ResourceList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Resource"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"ResourceTypeList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ResourceType"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"LocationList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Location"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"SetupList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Setup"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"ScheduleList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Schedule"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"ScheduleExceptionList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ScheduleException"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"HolidayList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Holiday"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"},"years":{"type":"array","items":{"type":"integer"},"description":"Hvilke år der overhovedet er fyldt ud — så et UI kan tilbyde det næste."}}},"PriceRule":{"type":"object","description":"SPEC 6.12. `condition` og `effect` er et lukket JSON-DSL — ingen kodeeksekvering.","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["price_rule"]},"label":{"type":"string","description":"Står på kundens prisforklaring."},"priority":{"type":"integer","description":"Stigende evalueringsorden: grundbeløb lavt, tillæg og rabatter højere.","examples":[200]},"offering_id":{"type":["string","null"],"description":"`null` = gælder alle ydelser."},"condition":{"$ref":"#/components/schemas/PriceCondition"},"effect":{"$ref":"#/components/schemas/PriceEffect"},"participant_category_id":{"type":["string","null"],"description":"Sat gør en `per_pax`-effekt til en pris kun for den kategori."},"active_from":{"type":["integer","null"]},"active_to":{"type":["integer","null"]},"is_active":{"type":"boolean"},"updated_at":{"type":"integer"}}},"PriceRuleInput":{"type":"object","required":["label","effect"],"properties":{"label":{"type":"string","maxLength":120},"priority":{"type":"integer","minimum":0,"maximum":10000,"default":100},"offering_id":{"type":["string","null"]},"condition":{"$ref":"#/components/schemas/PriceCondition"},"effect":{"$ref":"#/components/schemas/PriceEffect"},"participant_category_id":{"type":["string","null"]},"active_from":{"type":["string","null"],"format":"date-time"},"active_to":{"type":["string","null"],"format":"date-time","description":"`active_from`/`active_to` er hvornår REGLEN er i kraft — en kampagne huset kører i maj. Skal reglen kun prissætte bookinger i en sæson, siger den det med en betingelse på `line.start.date`; de to er reelt forskellige spørgsmål."},"is_active":{"type":"boolean","default":true}}},"PriceCondition":{"type":["object","null"],"description":"`{\"all\":[…]}` · `{\"any\":[…]}` · `{\"not\":{…}}` · eller et blad `{\"field\":…, \"<op>\":…}`.\n`null` betyder \"altid\" — en grundpris har ingen betingelse.\n\nOperatorer: `eq` `ne` `gt` `gte` `lt` `lte` `in` `not_in` `contains` `between`.\nFelter: `line.start.weekday` `line.start.date` `line.start.month`\n`line.start.minute_of_day` `line.duration_min` `line.pax_total` `line.offering_id`\n`line.setup_id` `line.resource_ids` `line.resource_tags` `line.resource_units`\n`booking.channel` `booking.is_holiday` `booking.holiday_name` `booking.discount_code`\n`customer.tags` samt `participants.<kategori>`.\n\nEt ukendt felt eller en ukendt operator er 422 ved skrivning — aldrig en regel der stille\ningenting gør.","additionalProperties":true,"examples":[{"all":[{"field":"line.start.weekday","in":["sat","sun"]},{"field":"line.pax_total","gte":10}]}]},"PriceEffect":{"type":"object","description":"SPEC 6.12's effekttyper, komplet og lukket: `base_amount` `per_pax`\n`per_participant_category` `per_time_unit` `per_resource_unit` `surcharge_pct`\n`surcharge_amount` `discount_pct` `discount_amount` `override` `tier` `minimum_spend`.\n\nBeløb er heltal i øre. Procent afrundes på LINJENS totalbeløb, half-up — aldrig pr. enhed.","required":["type"],"properties":{"type":{"type":"string"},"value":{"type":"number","description":"Øre for beløb, hele procenter for procent."},"unit_min":{"type":"integer","description":"`per_time_unit`: længden på én påbegyndt enhed."},"values":{"type":"object","additionalProperties":{"type":"integer"},"description":"`per_participant_category`: kategori-nøgle -> øre pr. deltager."},"tiers":{"type":"array","description":"`tier`: trappe på gruppestørrelse. Under første trin bidrager reglen intet.","items":{"type":"object","properties":{"from_pax":{"type":"integer"},"unit_amount_minor":{"type":"integer"}}}}},"examples":[{"type":"surcharge_pct","value":25}]},"PriceRuleList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PriceRule"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"},"vocabulary":{"type":"object","description":"Kun med `?include=vocabulary`: hele det lukkede DSL som data.","additionalProperties":true}}},"QuoteInput":{"type":"object","required":["offering_id","starts_at"],"properties":{"offering_id":{"type":"string"},"starts_at":{"type":"string","format":"date-time","description":"RFC 3339 **med offset** (SPEC 4.2). Et naivt tidsstempel er 422 overalt."},"duration_minutes":{"type":"integer","description":"Overskriver ydelsens egen varighed."},"pax":{"type":"integer"},"participants":{"type":"object","additionalProperties":{"type":"integer"},"description":"Deltagerkategori-NØGLE -> antal, samme form som `/availability` tager.","examples":[{"pc_adult":40,"pc_child":5}]},"setup_id":{"type":["string","null"]},"customer_id":{"type":["string","null"]},"customer_tags":{"type":"array","items":{"type":"string"}},"resource_ids":{"type":"array","items":{"type":"string"}},"discount_code":{"type":["string","null"]}}},"Quote":{"type":"object","description":"Bookingens pengehalvdel, uden en booking. Samme form gør at ét UI forklarer både et tilbud og en booket linje.","properties":{"object":{"type":"string","enum":["quote"]},"offering_id":{"type":"string"},"offering_name":{"type":"string"},"start":{"type":"string","format":"date-time"},"end":{"type":"string","format":"date-time"},"timezone":{"type":"string"},"pax_total":{"type":"integer"},"holiday":{"type":["string","null"],"description":"Helligdagens navn, hvis starten ligger på en."},"lines":{"type":"array","items":{"type":"object","properties":{"offering_id":{"type":"string"},"pax_total":{"type":"integer"},"line_total_minor":{"type":"integer"},"vat_rate_bp":{"type":"integer"},"price_explanation":{"type":"object","description":"SPEC 6.12: én række pr. regel der fyrede, i den orden den fyrede.","properties":{"lines":{"type":"array","items":{"type":"object","properties":{"rule_id":{"type":["string","null"]},"label":{"type":"string"},"amount_minor":{"type":"integer"},"kind":{"type":"string"}}}}}}}}},"totals":{"type":"object","properties":{"currency":{"type":"string"},"subtotal_minor":{"type":"integer"},"vat_minor":{"type":"integer"},"total_minor":{"type":"integer"},"paid_minor":{"type":"integer"},"balance_due_minor":{"type":"integer"}}},"skipped":{"type":"array","description":"Regler der blev set på og ikke gjaldt — den anden halvdel af \"hvorfor?\".","items":{"type":"object","properties":{"rule_id":{"type":"string"},"label":{"type":"string"},"reason":{"type":"string"}}}}}},"ParticipantCategoryInput":{"type":"object","required":["key","label"],"properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9_]{1,39}$"},"label":{"type":"string"},"min_age":{"type":["integer","null"]},"max_age":{"type":["integer","null"]},"counts_toward_capacity":{"type":"boolean","default":true,"description":"SPEC 6.6: en deltager der ikke optager en plads — et medfølgende spædbarn, en chauffør der venter i bussen — må ikke lægges ind i det tal lokalet måles mod."},"sort_order":{"type":"integer"}}},"FieldDefInput":{"type":"object","required":["entity","key","label","type"],"description":"SPEC 3 lag 2. Typer: `text` `textarea` `number` `decimal` `money` `boolean` `date` `time` `datetime` `select` `multiselect` `email` `phone` `url` `cvr` `ean` `address` `file` `signature` `resource_ref` `user_ref`.","properties":{"entity":{"type":"string","enum":["booking","booking_line","customer","resource","offering"]},"key":{"type":"string","pattern":"^[a-z][a-z0-9_]{1,59}$"},"label":{"type":"string"},"type":{"type":"string"},"required":{"type":"boolean"},"visibility":{"type":"string","enum":["public","admin","both"]},"show_if":{"type":["object","null"],"description":"Ét niveau, uden nesting, og kun mod et ANDET spørgsmål på samme entitet: en betingelse der peger på sig selv er en uendelig løkke i enhver renderer.","properties":{"field":{"type":"string"},"eq":{},"ne":{},"in":{"type":"array","items":{}}}},"options":{"type":["array","null"],"description":"Påkrævet på `select`/`multiselect`, forbudt på resten.","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}}}},"validation":{"type":["object","null"],"additionalProperties":true},"scope_offering_id":{"type":["string","null"]},"in_beo":{"type":"boolean","description":"SPEC 6.13: kommer svaret med på køreplanen."},"indexed":{"type":"boolean"},"sort_order":{"type":"integer"}}},"MessageTemplate":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["message_template"]},"key":{"type":"string","enum":["booking.confirmed","booking.reminder","booking.option_expiring","booking.guarantee_due","booking.cancelled"]},"channel":{"type":"string","enum":["email","sms"]},"subject":{"type":["string","null"]},"body":{"type":"string"},"offset_min":{"type":["integer","null"],"description":"Minutter fra reglens anker; negativ betyder \"før\". `null` = fyrer straks.","examples":[-1440]},"is_active":{"type":"boolean"},"updated_at":{"type":"integer"}}},"MessageTemplateInput":{"type":"object","required":["key","body"],"properties":{"key":{"type":"string"},"channel":{"type":"string","enum":["email","sms"],"default":"email"},"subject":{"type":["string","null"]},"body":{"type":"string","description":"Pladsholdere fra en lukket liste: `{{name}}` `{{reference}}` `{{start}}` `{{start_time}}` `{{end}}` `{{pax}}` `{{offering}}` `{{resource}}` `{{option_expires_at}}` `{{guarantee_due_at}}` `{{total}}` `{{venue}}` `{{manage_url}}`."},"offset_min":{"type":["integer","null"]},"is_active":{"type":"boolean","default":true}}},"MessageTemplatePatch":{"type":"object","properties":{"subject":{"type":["string","null"]},"body":{"type":"string"},"offset_min":{"type":["integer","null"]},"is_active":{"type":"boolean"}}},"MessageTemplateList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/MessageTemplate"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"},"rules":{"type":"array","description":"De fem regler med deres anker, så en klient ikke holder sin egen liste.","items":{"type":"object","properties":{"key":{"type":"string"},"anchor":{"type":"string","enum":["immediate","booking_start","option_expires","guarantee_due"]},"defaultOffsetMin":{"type":["integer","null"]}}}},"placeholders":{"type":"array","items":{"type":"string"}},"delivery_enabled":{"type":"boolean","description":"False indtil afsenderen findes."},"delivery_note":{"type":"string"}}},"Settings":{"type":"object","properties":{"object":{"type":"string","enum":["settings"]},"modules":{"type":"array","items":{"type":"string"},"description":"SPEC 3 lag 5. Det der ikke står her, findes ikke — heller ikke som endpoint."},"available_modules":{"type":"array","items":{"type":"string"}},"required_modules":{"type":"array","items":{"type":"string"},"examples":[["booking"]]},"payment":{"type":"object","description":"SPEC 6.14. `methods` er REGISTRERINGSmetoder — der er ingen gateway i fase 1.","properties":{"methods":{"type":"array","items":{"type":"string","enum":["manual_bank","ean_invoice","cash","terminal","giftcard"]}},"deposit_pct":{"type":["integer","null"]},"deposit_due_days":{"type":["integer","null"]},"invoice_due_days":{"type":["integer","null"]},"require_ean_for_public_customers":{"type":"boolean"}}},"available_payment_methods":{"type":"array","items":{"type":"string"}},"booking_page":{"type":"object","properties":{"slug":{"type":"string","pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},"headline":{"type":["string","null"]},"intro":{"type":["string","null"]},"accent":{"type":["string","null"],"pattern":"^#[0-9a-fA-F]{6}$"},"logo_url":{"type":["string","null"]},"contact_email":{"type":["string","null"]},"contact_phone":{"type":["string","null"]},"show_prices":{"type":"boolean"}}},"no_show":{"type":"object","description":"SPEC §7 fase 2: automatiske konsekvenser af gentagne udeblivelser. Svares ALTID hel og normaliseret. Der findes bevidst ingen nøgle der spærrer en kunde.","properties":{"enabled":{"type":"boolean","default":false},"threshold":{"type":"integer","minimum":1,"maximum":20,"default":3},"window_months":{"type":"integer","minimum":1,"maximum":60,"default":12},"require_manual_confirmation":{"type":"boolean","default":true},"flag_risk":{"type":"boolean","default":true},"lift_after_months":{"type":"integer","minimum":1,"maximum":60,"default":12}}},"values":{"type":"object","additionalProperties":true,"description":"Øvrige nøgler, urørt."},"updated_at":{"type":"integer"}}},"SettingsPatch":{"type":"object","properties":{"modules":{"type":"array","items":{"type":"string"}},"payment":{"type":"object","additionalProperties":true},"booking_page":{"type":"object","additionalProperties":true},"documents":{"type":"object","additionalProperties":true},"max_active_holds":{"type":["integer","null"],"minimum":1,"maximum":50,"description":"SPEC 6.5: loftet på samtidige reservationer pr. session og pr. IP. Standard 3. `null` sætter den tilbage til standarden. Rammes loftet, svarer `POST /slot_holds` 429 `too_many_holds`."},"no_show":{"type":["object","null"],"additionalProperties":false,"description":"SPEC §7 fase 2. En PATCH af reglen: de felter der sendes, ændres, resten står. `null` sætter hele reglen tilbage til standarden — som er «gør ingenting». Konsekvensen ophæves af sig selv efter `lift_after_months` uden en ny udeblivelse.","properties":{"enabled":{"type":"boolean"},"threshold":{"type":"integer","minimum":1,"maximum":20},"window_months":{"type":"integer","minimum":1,"maximum":60},"require_manual_confirmation":{"type":"boolean"},"flag_risk":{"type":"boolean"},"lift_after_months":{"type":"integer","minimum":1,"maximum":60}}}}},"Terms":{"type":"object","properties":{"object":{"type":"string","enum":["terms"]},"blueprint_key":{"type":["string","null"],"description":"Den branchepakke standardordene kommer fra, eller `null`."},"data":{"type":"array","items":{"$ref":"#/components/schemas/Term"}},"groups":{"type":"array","description":"Grupperne i opsætningsskærmen, så en klient ikke behøver sin egen kopi.","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"}}}},"updated_at":{"type":"integer"}}},"Term":{"type":"object","properties":{"key":{"type":"string","examples":["booking","resource.room"]},"group":{"type":"string","enum":["core","resources","time","money","operations"]},"what":{"type":"string","description":"Hvad nøglen dækker, i en sætning."},"source":{"type":"string","enum":["system","blueprint","tenant"]},"forms":{"$ref":"#/components/schemas/TermForms"},"default_forms":{"$ref":"#/components/schemas/TermForms"},"system_forms":{"$ref":"#/components/schemas/TermForms"}}},"TermForms":{"type":"object","properties":{"singular":{"type":"string","examples":["arrangement"]},"singular_definite":{"type":"string","examples":["arrangementet"]},"plural":{"type":"string","examples":["arrangementer"]},"plural_definite":{"type":"string","examples":["arrangementerne"]},"genitive":{"type":"string","description":"Udledt: `-s`, apostrof efter s, x og z."},"gender":{"type":"string","enum":["common","neuter"],"description":"Udledt af bestemt ental. Afgør \"en/et\" og \"ny/nyt\"."}}},"TermsPatch":{"type":"object","required":["terms"],"properties":{"terms":{"type":"object","description":"`{ \"booking\": { … } }` retter ordet. `{ \"booking\": null }` nulstiller det.","additionalProperties":{"oneOf":[{"type":"object","required":["singular","singular_definite","plural","plural_definite"],"properties":{"singular":{"type":"string","maxLength":60},"singular_definite":{"type":"string","maxLength":60},"plural":{"type":"string","maxLength":60},"plural_definite":{"type":"string","maxLength":60},"genitive":{"type":["string","null"],"maxLength":60}}},{"type":"null"}]}}}},"User":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["user"]},"email":{"type":"string","format":"email"},"name":{"type":"string"},"role":{"type":"string","description":"Nøglen på en række i `role` — fx `admin`, `staff` eller husets egen."},"has_password":{"type":"boolean","description":"Hashen forlader aldrig serveren — kun dette flag."},"last_login_at":{"type":["integer","null"]},"archived":{"type":"boolean","description":"Deaktiveret. Rækken bliver, så audit-loggen kan slå navnet op."},"updated_at":{"type":"integer"}}},"UserInput":{"type":"object","required":["email","name"],"properties":{"email":{"type":"string","format":"email"},"name":{"type":"string"},"role":{"type":"string","default":"staff","description":"En `role.key`. Ukendt eller lukket rolle er 422 med listen over dem der findes."},"password":{"type":"string","minLength":12}}},"UserPatch":{"type":"object","properties":{"name":{"type":"string"},"role":{"type":"string","description":"En `role.key`."},"archived":{"type":"boolean","description":"`false` aktiverer en deaktiveret kollega igen."},"password":{"type":["string","null"],"minLength":12}}},"UserList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/User"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"Role":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["role"]},"key":{"type":"string","description":"Maskinnavnet — det `user.role` peger på. Ændres aldrig."},"label":{"type":"string"},"description":{"type":["string","null"]},"permissions":{"type":"array","items":{"type":"string","enum":["bookings.read","bookings.write","setup.manage","money.read","money.write","reports.read","api.manage","users.manage"]},"description":"Rettigheder i seks områder. `bookings.write` trækker `bookings.read` med."},"scopes":{"type":"array","items":{"type":"string"},"description":"Afledt: de SAMME scopes en API-nøgle bærer. En session og en nøgle håndhæves ét sted (aksiom 7)."},"is_system":{"type":"boolean","description":"`admin` og `staff` følger med og kan ikke slettes."},"user_count":{"type":["integer","null"],"description":"Kun med i listen."},"sort_order":{"type":"integer"},"archived":{"type":"boolean"},"updated_at":{"type":"integer"}}},"RoleInput":{"type":"object","required":["label"],"properties":{"label":{"type":"string","maxLength":80},"key":{"type":"string","description":"Udledes af etiketten hvis den udelades."},"description":{"type":["string","null"],"maxLength":300},"permissions":{"type":"array","items":{"type":"string","enum":["bookings.read","bookings.write","setup.manage","money.read","money.write","reports.read","api.manage","users.manage"]}},"sort_order":{"type":"integer"}}},"RolePatch":{"type":"object","properties":{"label":{"type":"string","maxLength":80},"description":{"type":["string","null"],"maxLength":300},"permissions":{"type":"array","items":{"type":"string","enum":["bookings.read","bookings.write","setup.manage","money.read","money.write","reports.read","api.manage","users.manage"]}},"sort_order":{"type":"integer"},"archived":{"type":"boolean"}}},"RoleList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Role"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"}}},"ApiKey":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["api_key"]},"name":{"type":"string"},"kind":{"type":"string","enum":["secret","publishable","restricted"],"description":"`restricted` (`lsr_`) er en hemmelig nøgle med håndplukkede scopes og evt. en udløbsdato (SPEC 4.2, beslutning #29)."},"mode":{"type":"string","enum":["live","test"]},"prefix":{"type":"string","examples":["lsk_test_"]},"last_four":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"origin_allowlist":{"type":["array","null"],"items":{"type":"string"},"description":"SPEC 4.2. Hvilke sider en `lspk_`-nøgle må bruges fra. `https://mch.dk` matcher sig\nselv; `*.mch.dk` matcher domænet og alt under det. `null` eller tom liste betyder\n\"ikke indsnævret\" — ikke \"afvis alt\".\n\nHåndhæves på `Origin`-headeren, som browseren selv sætter. Et serverkald uden header\nslipper igennem: nøglen står i en offentlig webside, så listen beskytter ikke mod\ncurl — den beskytter mod at en ANDEN webside bruger jeres nøgle."},"last_used_at":{"type":["integer","null"]},"expires_at":{"type":["integer","null"]},"created_at":{"type":"integer"},"revoked_at":{"type":["integer","null"]}}},"ApiKeyInput":{"type":"object","required":["name","scopes"],"properties":{"name":{"type":"string"},"kind":{"type":"string","enum":["secret","publishable","restricted"],"default":"secret"},"mode":{"type":"string","enum":["live","test"],"default":"test"},"scopes":{"type":"array","items":{"type":"string"},"minItems":1},"origin_allowlist":{"type":["array","null"],"items":{"type":"string"}},"expires_at":{"type":["integer","null"],"description":"Epoch-ms. `null` = udløber aldrig. En dato bagud afvises 422 — en nøgle der er død i det sekund den er lavet, er aldrig hvad nogen mente."}}},"ApiKeyOriginPatch":{"type":"object","required":["origin_allowlist"],"description":"Det ENESTE felt der kan patches på en nøgle. Scopes kan ikke ændres — se `updateApiKeyOrigins`.","properties":{"origin_allowlist":{"type":["array","null"],"items":{"type":"string","examples":["https://mch.dk","*.mch.dk"]},"maxItems":20}}},"ApiKeyCreated":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","properties":{"key":{"type":"string","description":"Klarteksten. Findes kun i dette ene svar."},"shown_once":{"type":"boolean","enum":[true]},"shown_once_note":{"type":"string"}}}]},"ApiKeyList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"},"available_scopes":{"type":"array","items":{"type":"string"}},"publishable_scopes":{"type":"array","items":{"type":"string"},"description":"Det en offentlig nøgle må få. Alt andet afvises med 422."}}},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["webhook_endpoint"]},"url":{"type":"string","format":"uri"},"event_types":{"type":"array","items":{"type":"string"},"examples":[["booking.*"]]},"api_version":{"type":["string","null"]},"status":{"type":"string","enum":["active","paused","disabled"]},"secret":{"type":"string","description":"`whsec_…` — nødvendig for at verificere `v1=`-signaturen."},"secret_previous":{"type":["string","null"],"description":"Den forrige hemmelighed, mens den stadig signerer. Er den sat, bærer hvert kald TO `v1=`-værdier."},"secret_previous_expires_at":{"type":["integer","null"]},"paused_at":{"type":["integer","null"]},"paused_reason":{"type":["string","null"],"enum":["gone","failing_72h",null],"description":"Hvorfor arbejderen pausede den. NULL når et menneske gjorde det."},"failing_since":{"type":["integer","null"]},"created_at":{"type":"integer"},"updated_at":{"type":"integer"}}},"WebhookRotateInput":{"type":"object","properties":{"expire_old_in":{"type":["string","null"],"default":"24h","examples":["24h","30m","7d"],"description":"Hvor længe den gamle hemmelighed også signerer. Højst 30 dage."}}},"WebhookPingResult":{"type":"object","properties":{"object":{"type":"string","enum":["ping"]},"event_id":{"type":"string"},"delivery_ids":{"type":"array","items":{"type":"string"}},"delivery":{"$ref":"#/components/schemas/WebhookDelivery"},"queued":{"type":"boolean","description":"True når endpointet er på pause, så pinget står i kø."},"note":{"type":"string"}}},"WebhookEndpointInput":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"Kun https, og ikke en privat vært."},"event_types":{"type":"array","items":{"type":"string"},"default":["*"]},"api_version":{"type":["string","null"]}}},"WebhookEndpointPatch":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"event_types":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["active","paused","disabled"]}}},"WebhookEndpointList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"},"available_event_types":{"type":"array","items":{"type":"string"}},"delivery_enabled":{"type":"boolean"},"delivery_note":{"type":"string"}}},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string","enum":["webhook_delivery"]},"endpoint_id":{"type":"string"},"event_id":{"type":"string"},"event_type":{"type":["string","null"]},"event_sequence":{"type":["integer","null"]},"attempt":{"type":"integer","description":"Antal leveringer gjort. Første forsøg er straks; derefter otte trin over ~46 timer (SPEC 4.9)."},"status":{"type":"string","enum":["pending","succeeded","failed","gone"],"description":"`pending` = prøver igen · `succeeded` = 2xx inden for 10 s · `failed` = stigen er brugt op · `gone` = modtageren svarede 410."},"response_status":{"type":["integer","null"]},"error":{"type":["string","null"]},"next_attempt_at":{"type":["integer","null"]},"attempted_at":{"type":["integer","null"]},"duration_ms":{"type":["integer","null"]},"delivered_at":{"type":["integer","null"]},"replayed_at":{"type":["integer","null"]},"replay_count":{"type":"integer"},"created_at":{"type":"integer"}}},"WebhookDeliveryList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"},"pending_events":{"type":"integer","description":"Hændelser der er skrevet og endnu ikke fanet ud til nogen."},"queued_deliveries":{"type":"integer"},"next_attempt_at":{"type":["integer","null"]},"delivery_enabled":{"type":"boolean"},"delivery_note":{"type":"string"},"retry_schedule_ms":{"type":"array","items":{"type":"integer"}},"timeout_ms":{"type":"integer"},"retention_days":{"type":"integer"},"max_parallel_per_endpoint":{"type":"integer"}}},"Event":{"type":"object","required":["id","object","type","sequence","entity","entity_id","data"],"description":"SPEC 4.9. Præcis den konvolut der leveres som webhook — samme `id`, samme `sequence`, samme `data.object`.","properties":{"id":{"type":"string","examples":["evt_01JZQ7X4M2"]},"object":{"type":"string","enum":["event"]},"type":{"type":"string","examples":["booking.confirmed"]},"sequence":{"type":"integer","description":"Monotont pr. organisation. SPEC 4.9 giver INGEN rækkefølgegaranti på leveringen — det her er tallet man ordner efter, og genoptager på."},"created":{"type":["string","null"],"format":"date-time","examples":["2026-09-07T09:00:00+02:00"],"description":"Hvornår hændelsen skete, RFC 3339 med offset."},"entity":{"type":"string","examples":["booking"]},"entity_id":{"type":"string"},"data":{"type":"object","properties":{"object":{"type":"object","additionalProperties":true}}},"dispatched_at":{"type":["integer","null"]}}},"EventList":{"type":"object","description":"Cursor-pagineret liste (SPEC 4.2).","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"url":{"type":"string"},"latest_sequence":{"type":"integer","description":"Højeste sekvensnummer i organisationen — svaret på \"er jeg ajour?\"."},"available_event_types":{"type":"array","items":{"type":"string"}},"retention_days":{"type":"integer"}}},"EventReplayInput":{"type":"object","properties":{"endpoint_id":{"type":["string","null"],"description":"Send kun til dette endpoint. Udeladt: følg abonnementerne, som originalen gjorde."},"deliver_now":{"type":"boolean","default":true,"description":"Sæt false for kun at lægge den i kø — nyttigt ved en bulk-indhentning."}}},"TestClock":{"type":"object","description":"Sandkassens ur (SPEC 4.1 #10). Findes kun for testnøgler.","properties":{"object":{"type":"string","enum":["test_clock"]},"mode":{"type":"string","enum":["test"]},"now":{"type":"string","format":"date-time","description":"Hvad sandkassen tror klokken er. RFC 3339 med offset."},"wall_clock_now":{"type":"string","format":"date-time","description":"Hvad klokken faktisk er."},"offset_ms":{"type":"integer","description":"Millisekunder lagt til vægur-tiden. 0 = uret følger virkeligheden."},"advanced":{"type":"string","description":"Sidste forskydning, fx `15d`. Kun på POST."},"ticks":{"type":"integer","description":"Hvor mange forfaldstidspunkter uret standsede ved undervejs. Kun på POST."},"truncated":{"type":"boolean","description":"Blev etapelisten klippet (loft), så ikke alt nåede at forfalde."},"jobs":{"type":"object","additionalProperties":{"type":"integer"},"description":"Hvad der faldt ud af rejsen, pr. job — fx `optioner.udloebet: 1`."},"reset":{"type":"boolean","description":"Kun på DELETE."},"note":{"type":"string"},"url":{"type":"string"}},"required":["object","mode","now","wall_clock_now","offset_ms"]},"TestClockAdvance":{"type":"object","description":"SPEC 4.4: `POST /v1/test/clock {\"advance\":\"15d\"}`.","properties":{"advance":{"type":"string","description":"Tal + enhed: ms, s, m, h, d, w. Fx `15d`, `36h`, `90m`. Negativt ruller tilbage.","examples":["15d","36h","-2w"]}},"required":["advance"]},"EventReplayResult":{"type":"object","properties":{"object":{"type":"string","enum":["replay"]},"event":{"$ref":"#/components/schemas/Event"},"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}}}}}}