{"openapi":"3.1.0","info":{"title":"Tabletimer API","version":"1.0.0","description":"API til integration mellem Tabletimer og et hotel-PMS.\n\n**Autentificering.** Alle kald kræver `Authorization: Bearer <api-nøgle>`.\nNøglen oprettes under Indstillinger → Integration og vises kun én gang.\nHver nøgle hører til én restaurant og dækker alle dens rum.\n\n**Beløb** er altid i ØRE inkl. moms. **Datoer** er `YYYY-MM-DD` og henviser\ntil serviceaftenen, ikke til et klokkeslæt. **Tidsstempler** er ISO 8601 i UTC.\n\n**Rate limit.** 120 kald i minuttet pr. nøgle. Hvert svar bærer\n`X-RateLimit-Remaining`; ved 429 fortæller `Retry-After` hvor længe der skal ventes."},"servers":[{"url":"https://app.tabletimer.com"}],"tags":[{"name":"Nøgletal","description":"Tallene til et PMS-dashboard."},{"name":"Bookinger","description":"À la carte-bookinger."},{"name":"Ophold","description":"Flerdages ophold fra hotellets bookingsystem."},{"name":"Opslag","description":"Ledige tider og rum."}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API-nøglen fra Integration."}},"schemas":{"Reservation":{"type":"object","properties":{"id":{"type":"string"},"date":{"type":"string","format":"date","description":"Serviceaftenen, 'YYYY-MM-DD'."},"time":{"type":["string","null"],"description":"'HH:MM'. Null når tiden endnu ikke er aftalt (typisk et ophold)."},"turnMinutes":{"type":"integer","description":"Hvor længe bordet er sat af."},"covers":{"type":"integer"},"type":{"type":"string","enum":["ALA_CARTE","RESIDENT_INCLUDED","WALK_IN","EVENT"]},"status":{"type":"string","enum":["HELD","CONFIRMED","SEATED","COMPLETED","NO_SHOW","CANCELLED"]},"source":{"type":"string","enum":["WEB","STAFF","API"]},"areaId":{"type":"string"},"areaName":{"type":["string","null"]},"tableId":{"type":["string","null"]},"tableIds":{"type":"array","items":{"type":"string"},"description":"Alle bordene selskabet holder. Mere end ét betyder at bordene er skubbet sammen; tableId er hovedbordet."},"table":{"type":["string","null"],"description":"Bordets navn, fx \"12\" eller \"Bar 1\"."},"guest":{"type":["object","null"],"properties":{"name":{"type":"string"},"phone":{"type":["string","null"]},"email":{"type":["string","null"]}}},"roomBookingRef":{"type":["string","null"]},"roomNumber":{"type":["string","null"]},"isHotelGuest":{"type":"boolean"},"menuChoice":{"type":["string","null"]},"courses":{"type":["integer","null"]},"notes":{"type":["string","null"]},"specialRequests":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"confirmedAt":{"type":["string","null"],"format":"date-time"},"seatedAt":{"type":["string","null"],"format":"date-time"},"completedAt":{"type":["string","null"],"format":"date-time"},"cancelledAt":{"type":["string","null"],"format":"date-time"}}},"Room":{"type":"object","properties":{"id":{"type":"string"},"number":{"type":"string","description":"Værelsesnummeret som hotellet skriver det."},"name":{"type":["string","null"],"description":"For værelser der har et navn og ikke kun et nummer."},"floor":{"type":["string","null"]},"bedCount":{"type":["integer","null"]},"sortOrder":{"type":"integer"},"isActive":{"type":"boolean","description":"Falsk når værelset ikke længere er med i hotellets liste. Rækken bliver stående, så gamle ophold kan læses."},"externalId":{"type":["string","null"],"description":"Bookingsystemets eget id."}}},"Error":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string","description":"Menneskelæsbar besked. Kan omformuleres — forgren på `code`."},"code":{"type":"string","enum":["unauthorized","forbidden","not_found","invalid_request","conflict","rate_limited","internal_error"]},"details":{"description":"Feltfejl ved `invalid_request`, alternative tider ved `conflict`."}}},"Page":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"},"hasMore":{"type":"boolean"}}},"Metric":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"kind":{"type":"string","enum":["scalar","series","breakdown"]},"unit":{"type":"string","enum":["covers","currency","percent","count"],"description":"`currency` er ØRE — divider med 100 for kroner."},"requiresPos":{"type":"boolean"},"value":{"type":["number","null"],"description":"Kun ved kind=scalar."},"previousValue":{"type":["number","null"],"description":"Kun ved kind=scalar."},"changePct":{"type":["number","null"],"description":"Ændring vs. forrige periode, i procent."},"interval":{"type":"string","enum":["hour","day"],"description":"Kun ved kind=series."},"points":{"type":"array","description":"Kun ved kind=series.","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"value":{"type":"number"}}}},"rows":{"type":"array","description":"Kun ved kind=breakdown.","items":{"type":"object","properties":{"label":{"type":"string"},"value":{"type":"number"},"count":{"type":"integer","description":"Sekundært tal, fx antal solgte varer."}}}}}}},"responses":{"Unauthorized":{"description":"Nøglen mangler, er ukendt, udløbet eller spærret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"For mange kald. Se `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Findes ikke.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InvalidRequest":{"description":"Forespørgslen kunne ikke læses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/v1/availability":{"get":{"tags":["Opslag"],"summary":"Ledige tider","description":"Uden `area` svares for hele huset — summen af de rum der er åbne den dag. Med `area` kun dét rum.","parameters":[{"name":"date","in":"query","required":true,"schema":{"type":"string","format":"date"}},{"name":"covers","in":"query","required":true,"schema":{"type":"integer","minimum":1,"maximum":50}},{"name":"type","in":"query","schema":{"type":"string","enum":["ALA_CARTE","RESIDENT_INCLUDED","WALK_IN","EVENT"],"default":"ALA_CARTE"}},{"name":"area","in":"query","schema":{"type":"string"},"description":"Et areaId fra /areas."}],"responses":{"200":{"description":"Ledige tider for dagen."},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/areas":{"get":{"tags":["Opslag"],"summary":"Restaurantens rum","description":"Bruges til at sende `area` med på en booking, og til at forstå `areaId` på det der hentes tilbage.","responses":{"200":{"description":"Alle aktive rum.","content":{"application/json":{"schema":{"type":"object","properties":{"areas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"totalCoverCapacity":{"type":"integer"},"opensByDefault":{"type":"boolean","description":"False = rummet skal åbnes aktivt for dagen."},"sortOrder":{"type":"integer"},"isDefault":{"type":"boolean","description":"Hovedrummet."}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/metrics/catalog":{"get":{"tags":["Nøgletal"],"summary":"Hvilke nøgletal kan denne restaurant levere","description":"Byg jeres widget-vælger på denne — så slipper I for at vedligeholde listen to steder, og omsætningstal falder automatisk fra hos restauranter uden kassemodulet.","responses":{"200":{"description":"Kataloget.","content":{"application/json":{"schema":{"type":"object","properties":{"posEnabled":{"type":"boolean"},"periods":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"}}}},"metrics":{"type":"array","items":{"$ref":"#/components/schemas/Metric"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/metrics":{"get":{"tags":["Nøgletal"],"summary":"Nøgletal for en periode","description":"Beregnet med præcis samme kode som Tabletimers egen analyse-side, så de to skærme\nikke ender med hver sit tal for samme aften.\n\nVælg periode med `period` eller med `from`+`to`. Vælg tal med `metrics`;\nudelades den, svares med alt restauranten kan levere.","parameters":[{"name":"period","in":"query","schema":{"type":"string","enum":["today","7d","30d","month","prev_month"]},"description":"Standard er 30d. Ignoreres når from/to er sat."},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"metrics","in":"query","schema":{"type":"string"},"description":"Kommasepareret, fx `covers,revenue,noShowPct`.","example":"covers,revenue,noShowPct"}],"responses":{"200":{"description":"De ønskede nøgletal.","content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"object","properties":{"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"days":{"type":"integer"},"comparedTo":{"type":"object","description":"Perioden `previousValue` og `changePct` måles mod.","properties":{"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"}}}}},"metrics":{"type":"array","items":{"$ref":"#/components/schemas/Metric"}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/metrics/daily":{"get":{"tags":["Nøgletal"],"summary":"Nøgletal dag for dag som en flad tabel","description":"Samme tal som `/metrics`, men i rækker og kolonner — formen et BI-værktøj\n(Databox, Looker Studio, Power BI, Grafana) kan mappe uden hjælp.\n\nTomme dage er med som nuller, så en lukkedag tegnes som et hul i grafen\nfrem for at blive sprunget over. Beløb er i ØRE med mindre `amounts=dkk`.","parameters":[{"name":"period","in":"query","schema":{"type":"string","enum":["today","7d","30d","month","prev_month"]},"description":"Standard er 30d. Ignoreres når from/to er sat."},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"columns","in":"query","schema":{"type":"string"},"description":"Kommasepareret, fx `date,covers,revenue`. Udeladt betyder alle.","example":"date,covers,revenue"},{"name":"amounts","in":"query","schema":{"type":"string","enum":["ore","dkk"],"default":"ore"},"description":"Vælg `dkk` når værktøjet ikke selv kan dividere med 100."}],"responses":{"200":{"description":"Én række pr. dag i perioden.","content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"object","properties":{"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"days":{"type":"integer"}}},"columns":{"type":"array","description":"Kolonnerne i rækkefølge, med label og enhed.","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"unit":{"type":"string","enum":["text","covers","count","percent","currency","currency_dkk"]},"requiresPos":{"type":"boolean"}}}},"rows":{"type":"array","items":{"type":"object","description":"Nøglerne er kolonnernes `key`. Kassetal er null uden kassemodulet.","additionalProperties":{"type":["string","number","null"]}}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/reservations":{"get":{"tags":["Bookinger"],"summary":"Find bookinger","description":"På tværs af alle restaurantens rum. `updatedSince` er den nyttige til at holde sig ajour: den henter kun det der er ændret siden sidste kørsel.","parameters":[{"name":"date","in":"query","schema":{"type":"string","format":"date"},"description":"Én dag. Kan ikke kombineres med from/to."},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"status","in":"query","schema":{"type":"string"},"description":"Kommasepareret, fx `CONFIRMED,SEATED`."},{"name":"areaId","in":"query","schema":{"type":"string"}},{"name":"roomBookingRef","in":"query","schema":{"type":"string"},"description":"Alle dage i ét ophold."},{"name":"updatedSince","in":"query","schema":{"type":"string","format":"date-time"},"description":"Kun bookinger ændret efter dette tidspunkt."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Bookingerne.","content":{"application/json":{"schema":{"type":"object","properties":{"reservations":{"type":"array","items":{"$ref":"#/components/schemas/Reservation"}},"page":{"$ref":"#/components/schemas/Page"}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Bookinger"],"summary":"Opret en booking","description":"Oprettes og bekræftes i ét kald. Send `idempotencyKey`, hvis kaldet kan blive gentaget — samme nøgle giver samme booking i stedet for to.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["date","time","covers","guest"],"properties":{"date":{"type":"string","format":"date"},"time":{"type":"string","pattern":"^\\d{2}:\\d{2}$","example":"18:30"},"covers":{"type":"integer","minimum":1,"maximum":50},"type":{"type":"string","enum":["ALA_CARTE","RESIDENT_INCLUDED","WALK_IN","EVENT"],"default":"ALA_CARTE"},"guest":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string","format":"email"}}},"notes":{"type":"string"},"specialRequests":{"type":"string"},"roomBookingRef":{"type":"string"},"roomNumber":{"type":"string","maxLength":40},"idempotencyKey":{"type":"string"}}}}}},"responses":{"201":{"description":"Bookingen blev oprettet og bekræftet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reservation"}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"Der er ikke plads. `details.alternativeSlots` rummer de nærmeste ledige tider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/reservations/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Bookinger"],"summary":"Hent én booking","responses":{"200":{"description":"Bookingen.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reservation"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"tags":["Bookinger"],"summary":"Ret en booking","description":"Kun de sendte felter røres; `null` rydder et felt.\n\nKapaciteten regnes IKKE igennem — tjek `/api/v1/availability` først, hvis den skal respekteres.\n\n`status` kan kun sættes til CONFIRMED, CANCELLED eller NO_SHOW. At gæsten satte sig\n(SEATED) eller er færdig (COMPLETED) hører til i salen, hvor markeringen også starter\nbordets timer.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","minProperties":1,"properties":{"time":{"type":["string","null"],"pattern":"^\\d{2}:\\d{2}$"},"covers":{"type":"integer","minimum":1,"maximum":50},"status":{"type":"string","enum":["CONFIRMED","CANCELLED","NO_SHOW"]},"roomNumber":{"type":["string","null"],"maxLength":40},"menuChoice":{"type":["string","null"],"maxLength":120},"courses":{"type":["integer","null"],"minimum":1,"maximum":20},"notes":{"type":["string","null"],"maxLength":2000},"specialRequests":{"type":["string","null"],"maxLength":2000},"guest":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string","format":"email"}}}}}}}},"responses":{"200":{"description":"Den opdaterede booking.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reservation"}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"En aflyst booking kan ikke få ny status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Bookinger"],"summary":"Aflys en booking","description":"Frigiver pladsen. Rækken slettes ikke — restauranten skal kunne se, at der VAR en booking. Idempotent: en allerede aflyst booking svarer 200 med samme krop.","responses":{"200":{"description":"Bookingen er aflyst.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reservation"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/rooms":{"get":{"tags":["Ophold"],"summary":"Værelserne som Tabletimer kender dem","description":"Til at kontrollere at listen står som forventet, og til at læse `id` tilbage. `?includeInactive=true` tager også de værelser med, der er slået fra.","parameters":[{"name":"includeInactive","in":"query","schema":{"type":"boolean"},"description":"Tag også frakoblede værelser med."}],"responses":{"200":{"description":"Værelserne, sorteret som hotellet sendte dem.","content":{"application/json":{"schema":{"type":"object","properties":{"rooms":{"type":"array","items":{"$ref":"#/components/schemas/Room"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Ophold"],"summary":"Send hele værelseslisten","description":"Hele hotellets værelsesliste i ÉT kald. Værelser der ikke er med, slås fra —\nde slettes ikke, for gamle ophold og regninger peger på dem. Sendes et værelse\nigen senere, vågner den samme række.\n\nKaldet er idempotent: send den samme liste ti gange, og der sker det samme som\nførste gang. Værelsesnumre sammenlignes normaliseret, så \"12\" og \"Vær. 12\" er\nsamme værelse — står de begge i listen, afvises kaldet frem for at gætte.\n\nKaldet er valgfrit. Sender I kun ophold, opretter vi selv værelset ud fra\n`roomNumber` — så bliver listen bare ikke mere fuldstændig end de gæster,\nder har været her.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rooms"],"properties":{"rooms":{"type":"array","maxItems":2000,"items":{"type":"object","required":["number"],"properties":{"number":{"type":"string","maxLength":40,"description":"Fx \"12\" eller \"Suite 3\"."},"name":{"type":["string","null"],"maxLength":120},"floor":{"type":["string","null"],"maxLength":40},"bedCount":{"type":["integer","null"],"minimum":1,"maximum":20},"sortOrder":{"type":["integer","null"],"description":"Rækkefølgen i vælgeren. Udelades = listens egen orden."},"externalId":{"type":["string","null"],"maxLength":120},"isActive":{"type":"boolean","description":"Falsk for et værelse der findes, men ikke kan bruges (fx under renovering)."}}}}}}}}},"responses":{"200":{"description":"Hvad kaldet gjorde, og listen som den står bagefter.","content":{"application/json":{"schema":{"type":"object","properties":{"created":{"type":"integer"},"updated":{"type":"integer"},"deactivated":{"type":"integer","description":"Værelser der lå her i forvejen, men ikke var med i listen."},"rooms":{"type":"array","items":{"$ref":"#/components/schemas/Room"}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/stays":{"post":{"tags":["Ophold"],"summary":"Opret eller synkronisér et ophold","description":"Hver dag bliver til én reservation uden fast tid — gæsten vælger selv tidspunktet\nvia `bookingUrl` i svaret. Læg det link i jeres bekræftelsesmail.\n\nKaldet er idempotent på `roomBookingRef`: send det samme ophold igen, og dagene\nopdateres i stedet for at blive oprettet forfra. Dage der ikke længere er med,\naflyses. Udelades `roomNumber`, rører vi ikke ved et nummer personalet selv har skrevet.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["roomBookingRef","guest","days"],"properties":{"roomBookingRef":{"type":"string","maxLength":120,"description":"Jeres bekræftelsesnummer. Skal være det samme hver gang."},"roomNumber":{"type":["string","null"],"maxLength":40},"checkIn":{"type":["string","null"],"format":"date","description":"Ankomstdagen. Udelades den, bruger vi den første dag i days[]."},"checkOut":{"type":["string","null"],"format":"date","description":"Afrejsedagen. Udelades den, bruger vi dagen efter den sidste dag i days[]. Gæsten kan afregne til og med denne dag."},"status":{"type":"string","enum":["BOOKED","CHECKED_IN","CHECKED_OUT","CANCELLED"],"description":"Hvor gæsten er i sit ophold. Udelades den, lader vi den stå — bortset fra et aflyst ophold, der synkroniseres igen og dermed er genopstået."},"notes":{"type":["string","null"],"maxLength":500},"area":{"type":"string","description":"Et areaId fra /areas. Standard er hovedrummet."},"guest":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string","format":"email"}}},"days":{"type":"array","minItems":1,"maxItems":31,"items":{"type":"object","required":["date","covers"],"properties":{"date":{"type":"string","format":"date"},"covers":{"type":"integer","minimum":1,"maximum":50},"menuChoice":{"type":"string","maxLength":120},"courses":{"type":"integer","minimum":1,"maximum":20},"notes":{"type":"string","maxLength":500}}}}}}}}},"responses":{"201":{"description":"Opholdet. Svarer 201 både ved oprettelse og re-synkronisering.","content":{"application/json":{"schema":{"type":"object","properties":{"roomBookingRef":{"type":"string"},"roomNumber":{"type":["string","null"]},"room":{"oneOf":[{"$ref":"#/components/schemas/Room"},{"type":"null"}],"description":"Værelset i vores liste. Null indtil vi har fået et nummer."},"checkIn":{"type":"string","format":"date"},"checkOut":{"type":"string","format":"date"},"status":{"type":"string","enum":["BOOKED","CHECKED_IN","CHECKED_OUT","CANCELLED"]},"stayToken":{"type":"string"},"bookingUrl":{"type":["string","null"],"description":"Linket gæsten bruger til at vælge tid."},"guest":{"type":"object"},"days":{"type":"array","items":{"type":"object"}}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/stays/{ref}":{"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":"roomBookingRef, URL-kodet."}],"get":{"tags":["Ophold"],"summary":"Læs et ophold","responses":{"200":{"description":"Opholdet med alle dage."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Ophold"],"summary":"Aflys et ophold","description":"Aflyser alle opholdets dage og frigiver den reserverede plads.","responses":{"200":{"description":"Antal aflyste dage.","content":{"application/json":{"schema":{"type":"object","properties":{"cancelled":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"x-webhooks":{"description":"Tabletimer kalder jeres adresse ved disse hændelser. Opsæt dem under Indstillinger → Integration.","signature":{"header":"Tabletimer-Signature","format":"t=<unix-sekunder>,v1=<hex>","algorithm":"HMAC-SHA256 over strengen \"<t>.<rå body>\" med webhookens hemmelighed."},"events":[{"event":"reservation.created","description":"En booking blev oprettet","payload":"Reservation"},{"event":"reservation.updated","description":"En booking blev ændret (tid, antal, bord, noter)","payload":"Reservation"},{"event":"reservation.cancelled","description":"En booking blev aflyst","payload":"Reservation"},{"event":"reservation.seated","description":"Gæsten satte sig ved bordet","payload":"Reservation"},{"event":"reservation.completed","description":"Besøget blev afsluttet","payload":"Reservation"},{"event":"reservation.no_show","description":"Gæsten udeblev","payload":"Reservation"},{"event":"stay.created","description":"Et ophold blev oprettet","payload":"Stay"},{"event":"stay.updated","description":"Et ophold blev synkroniseret igen","payload":"Stay"},{"event":"stay.cancelled","description":"Et ophold blev aflyst","payload":"Stay"}]},"x-metrics":[{"key":"covers","label":"Kuverter","description":"Antal gæster i perioden. Aflyste bookinger tæller ikke med.","kind":"scalar","unit":"covers","requiresPos":false},{"key":"reservations","label":"Bookinger","description":"Antal bookinger der skulle møde op. Aflyste og uafsluttede hold tæller ikke med.","kind":"scalar","unit":"count","requiresPos":false},{"key":"avgBookingsPerDay","label":"Bookinger pr. dag","description":"Periodens bookinger delt med antal dage, lukkedage inklusive. Bookingen tæller på den dag gæsterne kommer.","kind":"scalar","unit":"count","requiresPos":false},{"key":"noShowPct","label":"No-show","description":"Andel af de fremmødeegnede bookinger, hvor gæsten udeblev.","kind":"scalar","unit":"percent","requiresPos":false},{"key":"guests","label":"Unikke gæster","description":"Antal forskellige gæster bag periodens bookinger.","kind":"scalar","unit":"count","requiresPos":false},{"key":"newGuests","label":"Nye gæster","description":"Gæster oprettet i kartoteket i perioden.","kind":"scalar","unit":"count","requiresPos":false},{"key":"revenue","label":"Omsætning","description":"Betalte regninger i perioden, i øre inkl. moms.","kind":"scalar","unit":"currency","requiresPos":true},{"key":"vat","label":"Moms","description":"Momsbeløbet af periodens betalte regninger, i øre.","kind":"scalar","unit":"currency","requiresPos":true},{"key":"bills","label":"Regninger","description":"Antal betalte regninger i perioden.","kind":"scalar","unit":"count","requiresPos":true},{"key":"avgSpendPerCover","label":"Forbrug pr. kuvert","description":"Omsætning delt med antal kuverter, i øre. Null når der ingen kuverter var.","kind":"scalar","unit":"currency","requiresPos":true},{"key":"coversSeries","label":"Kuverter over tid","description":"Kuverter pr. dag — eller pr. time når perioden er én enkelt dag.","kind":"series","unit":"covers","requiresPos":false},{"key":"revenueSeries","label":"Omsætning over tid","description":"Omsætning pr. dag — eller pr. time når perioden er én enkelt dag.","kind":"series","unit":"currency","requiresPos":true},{"key":"bookingTypes","label":"Bookingtyper","description":"Kuverter fordelt på à la carte, ophold, walk-in og events.","kind":"breakdown","unit":"covers","requiresPos":false},{"key":"bookingSources","label":"Bookingkilder","description":"Kuverter fordelt på online booking, personalet og PMS/API.","kind":"breakdown","unit":"covers","requiresPos":false},{"key":"weekdays","label":"Ugedage","description":"Kuverter fordelt på ugedag, mandag først.","kind":"breakdown","unit":"covers","requiresPos":false},{"key":"bookedBy","label":"Hvem tog bookingen","description":"Personalebookinger fordelt på kollega. Kun bookinger med spor i auditsporet tæller med.","kind":"breakdown","unit":"count","requiresPos":false},{"key":"topProducts","label":"Mest solgte varer","description":"De ti varer med størst omsætning i perioden, i øre.","kind":"breakdown","unit":"currency","requiresPos":true},{"key":"categories","label":"Varekategorier","description":"Omsætning fordelt på kategori, i øre.","kind":"breakdown","unit":"currency","requiresPos":true},{"key":"foodVsDrink","label":"Mad og drikke","description":"Omsætningen delt i mad og drikkevarer, i øre.","kind":"breakdown","unit":"currency","requiresPos":true},{"key":"payments","label":"Betalingsmetoder","description":"Omsætning fordelt på betalingsmetode, i øre.","kind":"breakdown","unit":"currency","requiresPos":true}]}