Javni API v1

Tehnička API specifikacija

Dobrodošli na portal za programere. Gost365 omogućava jednostavnu integraciju rezervacionog kalendara i formi na bilo kom sajtu (WordPress, Webflow, React, PHP ili statički HTML).

Autentifikacija

Svaki zahtev prema /api/v1/* mora sadržati dva zaglavlja: vaš javni API ključ i zaglavlje Origin sa domenom sa kog se poziv vrši.

Ključ vlasnik objekta generiše sam u Gost365 Dashboard-u pod Podešavanja → API, a može ga generisati i Super Admin iz Control Panela. Ima oblik gost365_live_ + 32 heksadecimalna karaktera (ukupno 45), npr. gost365_live_e83bc2a3a9f14c7b8d2e5a6f0b1c3d4e. Novi ključ važi tek nakon čuvanja forme, a čuvanjem stari prestaje da važi - integracija ne radi dok u njoj ne zamenite ključ. Poseban test ključ ne postoji: test režim se određuje statusom naloga, a ne prefiksom ključa.

Zaglavlja:

x-api-key: gost365_live_vaš_ključ
Authorization: Bearer gost365_live_vaš_ključ
Origin: https://vasdomen.com

Za ključ koristite jedno od prva dva zaglavlja. Ako pošaljete oba, x-api-key ima prednost i Authorization se ignoriše. Šema se poredi doslovno kao Bearer - veliko početno slovo i tačno jedan razmak; bearer malim slovima ili dupli razmak daju 401 Invalid API key.

Origin je obavezan - i proverava se pre API ključa.

Browser ga kod poziva na drugi domen postavlja automatski. Ali pozivi sa servera (PHP cURL, WordPress, Node backend, Postman) ne šalju Origin sam od sebe - morate ga dodati ručno. Zahtev bez njega se odbija sa 403 i telom { "error": "Missing Origin header" }, bez obzira na to što je ključ ispravan. Vrednost mora odgovarati nekom od domena upisanih u allowed_domains.

Pozive uvek šaljite preko HTTPS-a - ključ putuje u zaglavlju i preko običnog HTTP-a bi bio čitljiv u mreži. Napomena: sam API ne proverava protokol i ne odbija HTTP zahteve; HTTPS obezbeđuje hosting sloj, a ne validacija zahteva.

Autentifikacija prolazi ako je status naloga active, test ili trial (probni period od 14 dana od otvaranja naloga, tokom kojeg nalog radi punom snagom i prima prave rezervacije). Kada probni period istekne, API vraća 403 sa porukom Probni period je istekao. Kontaktirajte Gost365 za aktivaciju naloga. Za svaki drugi status API vraća 403 Account suspended. Kod naloga u statusu test sve email notifikacije odlaze na adresu superadmina umesto vlasniku objekta - koristite ga dok razvijate integraciju.

CORS & Whitelist Zaštita

Kako bi se sprečilo da neko zloupotrebi vaš javni API ključ na drugom sajtu, Gost365 poredi domen iz Origin zaglavlja sa listom dozvoljenih domena naloga. Lista se uređuje u Dashboard-u pod Podešavanja → API (polje allowed_domains), a može je postaviti i Super Admin iz Control Panela. Više domena odvaja se zarezom.

Kako se domeni porede

Pre poređenja se i dolazni Origin i svaki unos iz liste normalizuju: uklanjaju se razmaci, http:// odnosno https://, prefiks www., broj porta i završna kosa crta, i sve se prevodi u mala slova.

  • Jedan unos mojapartman.com pokriva i www.mojapartman.com, https://mojapartman.com/ i MOJAPARTMAN.COM.
  • Poddomeni se ne pokrivaju automatski - shop.mojapartman.com mora biti poseban unos.
  • Za lokalni razvoj upišite samo localhost - port se zanemaruje, pa taj unos pokriva svaki port. Ako lokalno razvijate dva različita sajta istog naloga, upišite ih sa portom (localhost:3004 i localhost:3005), jer se samo tako razlikuju i mogu da dobiju različite jedinice.
  • Unos * propušta sve domene. Koristite ga samo privremeno - javni ključ tada nije ničim vezan za vaš sajt.
  • Ako je lista prazna, nijedan domen ne prolazi i svaki poziv vraća 403.

Više sajtova na istom nalogu

Jedan nalog ima tačno jedan API ključ, pa svi sajtovi iz liste dozvoljenih domena koriste isti ključ. Vlasnik u svom panelu, na svakoj jedinici posebno, može da označi na kojim sajtovima se ona prikazuje. Zato isti ključ sa dva različita domena može da vrati različite jedinice.

  • Jedinica koja nije označena ni za jedan sajt ne prikazuje se nigde: API je ne vraća ni za jedan domen. Izuzeci: kada je u listi domena upisan *, i booking strana gost365.app/book, koja uvek prikazuje ceo nalog. Nova jedinica pri pravljenju dobija sve domene koje nalog tada ima.
  • Filtriranje ide po Origin zaglavlju, istom onom koje se proverava kroz listu domena, pa poddomen i ovde mora biti poseban unos.
  • Isto pravilo važi i za POST /api/v1/bookings: rezervacija za jedinicu koja ne pripada tom sajtu vraća 404, čak i ako znate njen id.
  • Raspored po sajtovima je interno podešavanje vlasnika i ne vraća se u odgovoru API-ja.
  • Ako vlasnik ukloni domen iz liste, jedinice koje su bile samo na njemu ne prikazuju se nigde dok im ne dodeli drugi sajt.
  • Ako je vlasnik za jedinicu uključio obaveštenje izdavaocu, rezervacija preko POST /api/v1/bookings šalje mejl i izdavaocu te jedinice. Odgovor API-ja se time ne menja, a podaci izdavaoca se nikad ne vraćaju sajtu.
Ako domen nije na listi, API vraća 403 sa telom { "error": "CORS origin ne dozvoljava pristup sa domena: ..." }. Provera se radi u samoj ruti - OPTIONS preflight zahtev prolazi bez provere ključa i domena i uvek vraća 204, pa neuspeh vidite tek na stvarnom GET ili POST pozivu.

Rate Limiting (Ograničenje)

U produkciji je aktivna zaštita od preopterećenja koja dozvoljava maksimalno 100 zahteva u minuti po kombinaciji IP adrese i API ključa. Kvota se vodi po tom paru, a ne po samoj IP adresi - različiti ključevi sa iste IP adrese imaju odvojene kvote. Ukoliko pređete limit, API vraća 429 sa telom { "error": "Too many requests" }.

Šta se zapravo broji

Limit se meri u kliznom prozoru od 60 sekundi, brojanjem zapisa u internom logu zahteva. Uspešni GET zahtevi se iz razloga performansi ne loguju, pa praktično ne troše kvotu. U kvotu ulaze uspešni POST zahtevi (rezervacije, upiti, forme) i svi odbijeni zahtevi (401 i 403), uključujući neuspele GET pozive. Sami 429 odgovori se ne broje, pa se blokada ne produžava sama od sebe.

U praksi: čitanje dostupnosti je praktično neograničeno, ali imate najviše 100 slanja rezervacija (ili 100 odbijenih zahteva) u minuti po paru IP + ključ.

IP adresa se očitava iz zaglavlja x-real-ip, a ako ga nema iz prvog elementa x-forwarded-for. Ako provera limita ne uspe zbog greške u bazi, zahtev se propušta (fail-open) umesto da se blokira.

API Reference: Jedinice

Dohvatite rezervacione jedinice klijenta i njihove zauzete termine. Jedini query parametar je opcioni include, sa vrednostima amenities_detailed (opisan niže), availability_mode i domacin, odvojenim zarezom. Samo polje status se ne vraća u odgovoru.

Jedinice bez kalendara na sajtu (availability_mode)

Vlasnik može za pojedinu jedinicu da isključi prikaz zauzetosti. Za takvu jedinicu availability_blocks je uvek prazan, bez obzira na parametre. Da biste razlikovali "nema rezervacija" od "zauzetost se ne prikazuje", tražite ?include=availability_mode: svaka jedinica tada dobija polje availability_mode.

  • "calendar": availability_blocks sadrži prave zauzete termine, prikažite kalendar.
  • "inquiry": prikažite formu za upit (gost bira željene datume, vlasnik potvrđuje) i nigde ne tvrdite da je termin slobodan. Slanje ide istim POST /bookings; ako je termin u sistemu već zauzet, odgovor je i dalje 409, pa gostu prikažite neutralnu poruku da termin nije dostupan.

Bez parametra polja nema, a odgovor je isti kao ranije.

Domaćin sa brendiranom stranicom (domacin)

Sa ?include=domacin svaka jedinica dobija polje domacin: null, ili objekat sa naslov (naziv brenda ili ime), logo_url (ili null), opis (do 160 znakova, ili null), adresa i broj_apartmana. Dobijaju ga samo jedinice sa uključenom brendiranom stranicom koje su na vašem domenu, i samo za domaćine koje je vlasnik naloga u panelu uključio za prikaz u katalogu. adresa je putanja stranice domaćina na vašem sajtu: /i/<adresa> kad ima bar dva apartmana, inače /a/<adresa>; sadržaj stranice dajte preko GET /stranice. Mejl domaćina se nikad ne vraća.

Koje jedinice se NE vraćaju

  • arhivirane i zaključane (status ARCHIVED ili INACTIVE): ne vraćaju se u spisku, ne primaju rezervacije i njihov iCal link ne daje termine,
  • jedinice čiji modul nije uključen na nalogu,
  • jedinice koje je vlasnik sakrio sa sajta u podešavanjima jedinice.

Nova jedinica koju vlasnik sam doda u Dashboard-u je na početku sakrivena sa sajta, dok je ne popuni i ne uključi prikaz. Ako vam jedinica ne stiže, prvo proverite to.

GEThttps://gost365.app/api/v1/units

Primer odgovora (200 OK) - odaberite tip jedinice

Primer prikazuje odgovor za GET /api/v1/units?include=amenities_detailed. Bez tog parametra polje amenities_detailed ne postoji, sve ostalo je isto.

Smeštaj

Vozila

Oprema

Ugostiteljstvo

{
  "success": true,
  "language": "sr",
  "data": [
    {
      "id": "8f7e6d5c-4b3a-2a19-0e9d-8c7b6a5f4e3d",
      "name": "Apartman Morski Konjic",
      "type": "Apartman",
      "billing_mode": "nightly",
      "description": "Luksuzan studio sa pogledom na more.",
      "address": "Bulevar Plaže 12, 85310, Budva, Crna Gora",
      "base_price": 75,
      "check_in": "14:00",
      "check_out": "10:00",
      "amenities": ["wifi", "ac", "pool", "fridge"],
      "amenities_detailed": [
              {
                      "id": "wifi",
                      "label": "WiFi",
                      "group": "Opšte",
                      "icon": "wifi"
              },
              {
                      "id": "ac",
                      "label": "Klima",
                      "group": "Opšte",
                      "icon": "air-vent"
              },
              {
                      "id": "pool",
                      "label": "Bazen",
                      "group": "Opšte",
                      "icon": "waves"
              },
              {
                      "id": "fridge",
                      "label": "Frižider",
                      "group": "Kuhinja",
                      "icon": "refrigerator"
              }
      ],
      "details": {
            "location": {
                  "street": "Bulevar Plaže 12",
                  "city": "Budva",
                  "postalCode": "85310",
                  "country": "Crna Gora"
            },
            "max_guests": "4",
            "bedrooms": "2",
            "beds": "3",
            "living_rooms": "1",
            "bathrooms": "1",
            "terraces": "1",
            "balconies": "0",
            "parking_spots": "1",
            "kitchen_type": "open",
            "area_size": "65",
            "floor": "2. sprat",
            "total_floors": "4",
            "house_rules": "Tiho posle 22h. Ljubimci uz dogovor.",
            "checkin_instructions": "Ključ se uzima u kafiću u dvorištu."
      },
      "images": ["https://<vaš-supabase-projekat>.supabase.co/storage/v1/object/public/units/slika1.jpg"],
      "pricing_config": {
        "base_price": 75,
        "hourly_price": null,
        "currency": "EUR",
        "tiers": [],
        "seasons": [],
        "extra_guest_threshold": null,
        "extra_guest_charge": null,
        "cleaning_fee": null,
        "addons": [
          { "id": "a1b2c3", "label": "Doručak", "price": 7, "charge": "per_guest_per_night", "required": false, "default_on": true }
        ]
      },
      "availability_blocks": [
        { "start_date": "2026-07-01T00:00:00+00:00", "end_date": "2026-07-08T00:00:00+00:00", "status": "confirmed" },
        { "start_date": "2026-07-15T00:00:00+00:00", "end_date": "2026-07-20T00:00:00+00:00", "status": "pending" }
      ]
    }
  ]
}
availability_blocks sadrži potvrđene rezervacije i rezervacije na čekanju (status: "confirmed" ili "pending"); otkazane (cancelled) se ne vraćaju. Termin poslat kroz POST /api/v1/bookings kreira se kao pending i odmah se vidi kao zauzet, jer sistem ne prima drugi zahtev za isti termin. Kad vlasnik zahtev odbije ili otkaže, termin se oslobađa. Odbijanje sa 409 je i dalje moguće ako dva gosta pošalju zahtev istovremeno - obavezno obradite taj status u UI-ju.

start_date i end_date u blokovima uvek stižu kao ISO datetime sa vremenskom zonom (npr. "2026-07-01T10:00:00+00:00") - i kod dnevnih tipova, gde je vreme 00:00:00. Za satne tipove vreme je značajno i blokira tačan interval. Ako vam treba samo datum, uzmite String(v).split('T')[0].

Termin se smatra zauzetim po pravilu preklapanja: postojeći blok blokira novi zahtev samo ako se intervali stvarno seku. Zato check-out jednog gosta i check-in drugog mogu biti isti dan, a dva satna termina isti dan ne smetaju jedan drugom ako se ne preklapaju.

Polje address je ravan string koji sistem automatski sastavlja iz strukturirane adrese, redosledom ulica, poštanski broj, grad, država. Ako vam adresa treba po delovima, koristite objekat details.location sa poljima street, city, postalCode, country. Top-level polje location ne postoji u odgovoru.

Polja check_in i check_out su podrazumevana vremena dolaska i odlaska za jedinicu (npr. "14:00"), namenjena prikazu na sajtu. Ona ne ograničavaju vrednosti koje šaljete u POST /api/v1/bookings - API ih pri kreiranju rezervacije ne proverava.

Polje description je slobodan tekst koji vlasnik piše sam i može da sadrži prelome redova (\n) kojima deli pasuse. Prikažite ga sa CSS pravilom white-space: pre-line, inače pregledač sve prelome pretvori u razmake i opis se slije u jedan pasus. Stariji zapisi mogu imati i \r\n; pre-line pravilno obrađuje oba.

Cenu ne morate računati sami: pricing_config je informativan, a konačan iznos server obračunava pri kreiranju rezervacije.

Ako cenu prikazujete unapred, pricing_config se čita ovako. base_price je osnovna cena po noći (satu, danu). tiers su cene po broju gostiju (odrasli + deca, bez beba): pravilo sa guests_exact važi za tačno toliko osoba i ima prednost, zatim prvo pravilo po redu sa guests_up_to ili guests_from koje odgovara. seasons su periodi sa svojom cenom (date_from i date_to uključivo, i sopstvena pravila po broju gostiju); sezona se bira prema datumu dolaska i važi za ceo boravak. Na kraju se dodaju cleaning_fee (jednom) i dodatne usluge iz addons.

Opremljenost. Polje amenities je niz id-jeva, npr. ["wifi", "fridge"]. Može biti prazno ili izostati. Id-jevi su vezani za tip jedinice i ne smeju se spajati između tipova: heating kod smeštaja znači „Grejanje", a kod bazena „Grijanje". Zato nemojte praviti sopstvenu tabelu id → naziv.

Umesto toga dodajte ?include=amenities_detailed na zahtev. Odgovor tada uz svaku jedinicu nosi i razrešen oblik, spreman za prikaz:

"amenities_detailed": [
  { "id": "wifi",   "label": "WiFi",      "group": "Opšte",   "icon": "wifi" },
  { "id": "fridge", "label": "Frižider",  "group": "Kuhinja", "icon": "refrigerator" }
]
  • label je na jeziku klijenta. Koji je to jezik piše u polju language na vrhu odgovora ("sr", "sl" ili "bs"). Polje language stiže uvek, i bez include.
  • group je naziv sekcije i uvek je na srpskom, bez obzira na jezik klijenta. Kod smeštaja su to Opšte, Kuhinja, Kupatilo, Spavaća soba, Dnevni boravak, Wellness, Spoljašnji prostor, Pogled, Porodica, Bezbednost, Pristupačnost, Usluge. Ako naslove sekcija prikazujete na drugom jeziku, prevedite ih sami. Može biti null kod tipova koji još nisu podeljeni na sekcije - tada prikažite sve u jednom bloku.
  • icon je stabilan naziv ikonice, npr. "refrigerator". Vrednosti se ne menjaju i poklapaju se sa nazivima iz skupa lucide, pa ako ga već koristite, mapiranje radi samo od sebe. Može biti null.
  • Grupišite po polju group, ne po redosledu. Stavke su uglavnom poređane po sekcijama, ali dodatne stavke pojedinih tipova (npr. doručak kod Sobe, roštilj kod Kuće) stižu na kraju spiska. Ko iscrtava naslov sekcije pri svakoj promeni grupe dobiće istu sekciju dva puta. Primer ispod grupiše ispravno.
Zašto je opciono. Polje amenities_detailed se šalje samo kada ga zatražite kroz include. Sajtovi koji su već napravljeni dobijaju isti oblik jedinica kao do sada, uz jedino novo polje language na vrhu odgovora.

Prikaz po sekcijama, kao na velikim sajtovima za smeštaj, tada staje u petnaestak linija:

const res = await fetch(
  'https://gost365.app/api/v1/units?include=amenities_detailed',
  { headers: { 'x-api-key': API_KEY } }
)
const { data } = await res.json()

for (const jedinica of data) {
  const sekcije = {}
  for (const a of jedinica.amenities_detailed ?? []) {
    const g = a.group ?? 'Ostalo'
    ;(sekcije[g] ??= []).push(a)
  }

  for (const [naziv, stavke] of Object.entries(sekcije)) {
    console.log(naziv)
    for (const a of stavke) {
      console.log('  ', IKONICE[a.icon] ?? '•', a.label)
    }
  }
}

Ako ne koristite biblioteku ikonica, prekopirajte ovu tabelu i gotovi ste:

const IKONICE = {
  'wifi': '📶', 'air-vent': '❄️', 'flame': '🔥', 'arrow-up-down': '🛗',
  'trees': '🌳', 'fence': '🏞️', 'circle-parking': '🅿️', 'warehouse': '🚘',
  'waves': '🏊', 'dog': '🐕', 'cigarette': '🚬', 'croissant': '🥐',
  'beef': '🍖', 'sprout': '🌱', 'chef-hat': '🍳', 'refrigerator': '🧊',
  'cooking-pot': '🍲', 'microwave': '🍱', 'utensils-crossed': '💧', 'coffee': '☕',
  'soup': '🥣', 'shower-head': '🚿', 'bath': '🛁', 'wind': '💨',
  'washing-machine': '🧺', 'layers': '🧖', 'sparkles': '✨', 'door-open': '🚪',
  'bed-double': '🛏️', 'shirt': '👕', 'bed-single': '🛌', 'baby': '👶',
  'tv': '📺', 'sofa': '🛋️', 'utensils': '🍽️', 'laptop': '💻',
  'fan': '🌀', 'volume-x': '🔇', 'door-closed': '🚪', 'plug-zap': '🔌',
  'snowflake': '❄️', 'glass-water': '🫖', 'sandwich': '🍞', 'blender': '🥤',
  'salad': '🧂', 'wine': '🍷', 'droplet': '💧', 'thermometer': '🌡️',
  'footprints': '🩴', 'blinds': '🪟', 'bed': '🛏️', 'monitor-play': '🎬',
  'satellite-dish': '📡', 'speaker': '🔊', 'book-open': '📚', 'dices': '🎲',
  'gamepad-2': '🎮', 'armchair': '🪑', 'dumbbell': '🏋️', 'heater': '♨️',
  'droplets': '🫧', 'sun': '☀️', 'umbrella': '⛱️', 'tree-palm': '🏖️',
  'sailboat': '⛵', 'mountain-snow': '🏔️', 'building-2': '🏙️', 'flower-2': '🌷',
  'signpost': '🚏', 'toy-brick': '🧸', 'puzzle': '🛝', 'shield': '🛡️',
  'alarm-smoke': '🚨', 'siren': '⚠️', 'fire-extinguisher': '🧯', 'briefcase-medical': '🩹',
  'vault': '🔐', 'cctv': '📹', 'lock-keyhole': '🔒', 'person-standing': '🚶',
  'accessibility': '♿', 'hand': '✋', 'key-round': '🔑', 'luggage': '🧳',
  'plane': '✈️', 'spray-can': '🧹', 'calendar-days': '📅', 'bike': '🚲',
  'concierge-bell': '🛎️',
}

API Reference: Rezervacije

Pošaljite zahtev za rezervaciju sa vašeg sajta u Dashboard. Rezervacija se uvek kreira sa statusom pending i izvorom website, a vlasniku objekta automatski stiže email za potvrdu - API ne kreira odmah potvrđenu rezervaciju i ta polja se ne mogu poslati kroz telo zahteva.

POSThttps://gost365.app/api/v1/bookings

Primer Request Body - odaberite tip jedinice

Smeštaj

Vozila

Oprema

Ugostiteljstvo

{
  "unit_id": "8f7e6d5c-4b3a-2a19-0e9d-8c7b6a5f4e3d",
  "start_date": "2026-07-01",
  "end_date": "2026-07-08",
  "guest_name": "Jovan Jovanović",
  "guest_email": "jovan.jovanovic@email.com",
  "guest_phone": "+381641234567",
  "adults_count": 2,
  "children_count": 1,
  "infants_count": 0,
  "notes": "Molimo za tihu sobu i krevetac za bebu."
}

Opis Polja (JSON Body)

PoljeTipObaveznoOpis
unit_idUUIDDaJedinstveni ID apartmana iz GET /units
start_dateString (YYYY-MM-DD ili YYYY-MM-DDTHH:mm:ss)DaDatum dolaska / početak termina. Za smeštaj i dnevne tipove dovoljan je datum (2026-07-01). Za tipove sa billing_mode: "hourly" šaljite datum i vreme sa vremenskom zonom. Neispravan datum vraća 400 Invalid date format.
end_dateString (YYYY-MM-DD ili YYYY-MM-DDTHH:mm:ss)DaDatum odlaska / kraj termina. Kod satnih tipova mora biti posle start_date, a kod ostalih ne sme biti pre njega - inače 400 end_date must be after start_date. Za satne termine obavezno pošaljite vremensku zonu (npr. 2026-07-01T19:00:00+02:00); vreme bez zone se čita kao UTC.
guest_nameStringDaIme i prezime gosta. Upisuje se najviše 200 znakova.
guest_emailStringDaKontakt email adresa gosta. Duža od 254 znaka vraća 400 Invalid guest_email.
guest_phoneStringNeKontakt telefon gosta. Prihvata se i kao broj; upisuje se najviše 60 znakova.
adults_countIntegerNe (podr. 1)Broj odraslih osoba. Mora biti veći od nule. Ako pošaljete 0, a children_count i infants_count izostavite, ukupan broj gostiju je 0 i baza odbija upis - dobijate 500, ne 400. Kada broj nije poznat, polje izostavite.
guests_countIntegerNe (zastarelo)Zadržano radi kompatibilnosti sa starijim integracijama. Koristi se samo ako adults_count nije poslat. Za nove integracije koristite razbijena polja.
children_countIntegerNe (podr. 0)Broj dece (2-17 godina)
infants_countIntegerNe (podr. 0)Broj beba (do 2 godine)
notesStringNeNapomena gosta. Upisuje se najviše 4000 znakova, ostatak se odbacuje.
service_typeStringNe (praktično obavezno za jedinice sa details.service_options)Naziv usluge iz details.service_options jedinice. Ako vrednost tačno odgovara nazivu jedne od opcija, cena se uzima iz te opcije: obračunava se kao 1 × cena usluge, trajanje termina se ignoriše i naknada za čišćenje se ne dodaje.
addonsArray<String>NeId-jevi dodatnih usluga iz pricing_config.addons koje je gost izabrao, npr. ["a1b2..."]. Šalju se samo id-jevi - cena se uvek uzima sa servera. Usluge sa required: true se naplaćuju i kada nisu poslate. Iznos se dodaje na cenu noćenja i ulazi u total_price. Ignoriše se kada je poslat važeći service_type.

Način naplate je u polju charge svake usluge: per_guest_per_night (po osobi po noći), per_night (po noći), per_guest (po osobi, jednom), once (jednom za ceo boravak). Bebe se ne računaju kao osobe. Kod satnih i dnevnih tipova „noć" znači sat, odnosno dan. Usluge bez naziva ili sa cenom 0 se ne naplaćuju.

Kada jedinica ima service_options

Ako jedinica u odgovoru GET /units ima details.service_options, uvek šaljite i service_type. Poređenje razlikuje velika i mala slova, a razmaci sa početka i kraja se zanemaruju na obe strane.

Ako se izostavi ili ne odgovara nijednoj opciji, cena se računa iz pricing_config. Kod jedinica bez base_price i bez odgovarajućeg tiera to daje 400 Pricing not configured for this unit, a kod jedinica koje imaju base_price dobićete rezervaciju sa pogrešno obračunatom cenom (po noći ili satu umesto fiksne cene usluge) - bez ikakve greške.

Poslata vrednost se uvek upisuje uz rezervaciju i pojavljuje se u email obaveštenju vlasniku, čak i kada ne odgovara nijednoj opciji. Zato proverite naziv.

Odgovor pri uspešnom kreiranju (201 Created)

{
  "success": true,
  "booking_id": "7a8b9c0d-e1f2-3a4b-5c6d-7e8f9a0b1c2d",
  "message": "Booking created successfully"
}

Greške i Statusni Kodovi

Svaki neuspešan odgovor ima polje { "error": "opis greške" } (kod Database transaction failed uz njega stiže i detail). Razlog uvek čitajte iz polja error, jer isti statusni kod pokriva više različitih uzroka.

  • 400 Missing required fields - nedostaje neko od obaveznih polja. Pricing not configured for this unit - cenovnik jedinice nije konfigurisan za dati broj gostiju i datum. Too many guests for this unit (maximum N) - zbir adults_count i children_count je veći od details.max_guests jedinice; bebe se ne računaju. Invalid date format - datum se ne može pročitati. end_date must be after start_date - kraj je pre početka (kod satnih tipova i jednak početku).
  • 401 Missing API key ili Invalid API key - ključ nije poslat ili ne odgovara nijednom nalogu.
  • 403 Četiri različita uzroka: Missing Origin header - zahtev nema Origin zaglavlje (pogađa server-to-server pozive, proverava se pre ključa). Probni period je istekao... - nalog je u statusu trial duže od 14 dana. Account suspended - status naloga nije active, test ni trial. CORS origin ne dozvoljava pristup sa domena: ... - domen nije na listi dozvoljenih.
  • 404 Unit not found or access denied - jedinica sa datim unit_id ne postoji, ne pripada vašem nalogu, deaktivirana je, njen modul nije uključen, ili ju je vlasnik sakrio sa sajta. Najčešća greška pri integraciji.
  • 409 Konflikt preklapanja (Booking overlap detected) - termin se preklapa sa postojećom rezervacijom te jedinice. Provera obuhvata sve rezervacije čiji status nije cancelled, dakle i one na čekanju koje GET /units ne vraća. Zato je 409 moguć i za termin koji na vašem kalendaru izgleda slobodno.
  • 429 Too many requests - pređen je limit od 100 zahteva u minuti po paru IP + ključ.
  • 500 Database transaction failed - upis u bazu je odbijen (npr. ukupan broj gostiju je 0). Internal Server Error - neočekivana greška, npr. neispravan JSON u telu zahteva.

Šta se dešava posle kreiranja

Rezervacija ostaje u statusu pending dok je vlasnik ne potvrdi ili otkaže u Dashboard-u. Do tada se ne pojavljuje u availability_blocks kroz GET /units, ali blokira preklapajuće termine i izaziva 409.

API ne nudi endpoint za čitanje, izmenu ni otkazivanje rezervacije. Sačuvajte booking_id iz odgovora za sopstvenu evidenciju, a dalji tok pratite kroz Dashboard.

Ostali endpointi

Pored jedinica i rezervacija, API izlaže i kontakt upite, prilagođene forme i iCal izvoz zauzetosti. Svi endpointi pod /api/v1/ traže ista zaglavlja kao i prethodni - API ključ i Origin.

GEThttps://gost365.app/api/v1/stranice/{adresa}?vrsta=a|i

Brendirane stranice na vašem sajtu. Ako vaš sajt ima rute /a/{adresa} (jedan apartman, za deljenje) i /i/{adresa} (svi apartmani jednog izdavaoca), ovde pitate šta da prikažete. Odgovor nosi samo unit_id i brend izdavaoca; samu jedinicu već imate iz GET /api/v1/units. Jedinica se vraća samo ako je vlasnik uključio Brendiranu stranicu i ako je čekirana za domen iz Origin.

// vrsta=a
{ "success": true, "data": {
  "vrsta": "a",
  "adresa": "apartman-marko",
  "unit_id": "3f2a1b0c-...",
  "izdavalac": {                 // null ako jedinica nema izdavaoca sa stranicom
    "naslov": "Apartmani Marko", // naziv brenda ili ime; null = koristite svoj logo
    "logo_url": "https://...",   // ili null
    "telefon": "+381 64 123 4567", // ili null
    "svi_apartmani": "apartmani-marko", // adresa za /i/, ili null
    "opis": "Apartmani na Kopaoniku, 5 minuta od žičare", // ili null
    "naslovna_url": "https://...", // naslovna slika, ili null
    "boja": "#0F6E56",             // boja brenda, ili null (vaša podrazumevana)
    "kanali": { "whatsapp": true, "viber": false }, // na čemu je telefon; dugme samo za true
    "font": "playfair",            // par fontova, ili null (vaš font)
    "ponude": [                    // ponude koje danas važe (najviše 3); [] = nema
      { "naslov": "Last minute ovog vikenda", "istaknuto": "-15%", "opis": "Za boravke od petka do nedelje.",
        "slika": "https://...", "cta": "Rezerviši", "sablon": "slika", "pozadina": "brend", "odbrojavanje": true, "animacija": "odsjaj", "apartman_adresa": "apartman-marko", "dolazak": "2026-11-24", "odlazak": "2026-11-27",
        "od": null, "do": "2026-10-20" }
    ],
    "razlika_booking": 15,         // na Bookingu oko 15% skuplje, ili null
    "pitanja": [                   // česta pitanja izdavaoca; [] = bez odeljka
      { "pitanje": "Do kada je prijava?", "odgovor": "Od 14 do 20 h." }
    ],
    "domacin": {                   // null kad ništa nije upisano
      "slika_url": "https://...",  // ili null
      "prica": "Zdravo, ja sam Marko...", // ili null; može imati nove redove
      "jezici": ["sr", "en"],      // ISO kodovi; nazive prikazujete vi
      "instagram": "apartmani.marko", // samo korisničko ime, ili null
      "facebook": "apartmani.marko",  // samo ime stranice, ili null
      "tiktok": "apartmani.marko",    // samo korisničko ime, ili null
      "youtube": "apartmanimarko",    // samo ime kanala, ili null
      "odgovor_sati": 2            // obično odgovara za oko 2 h; null bez dovoljno podataka
    }
  }
} }

// vrsta=i
{ "success": true, "data": {
  "vrsta": "i",
  "adresa": "apartmani-marko",
  "brend": {                     // ista polja kao "izdavalac" iznad, bez svi_apartmani
    "naslov": "Apartmani Marko", "logo_url": null, "telefon": null,
    "opis": null, "naslovna_url": null, "boja": null, "domacin": null,
    "kanali": { "whatsapp": true, "viber": true }, "font": null, "pitanja": [], "ponude": [], "razlika_booking": null
  },
  "jedinice": [{ "unit_id": "3f2a...", "adresa": "apartman-marko" }]
} }

// stara adresa, ili /i/ sa samo jednim apartmanom
{ "success": true, "data": { "preusmeri": "/a/nova-adresa" } }

404 Stranica nije pronađena. (isključena, nije na vašem domenu ili ne postoji) · 400 vrsta nije a ni i.

Sadržaj stranice (opis, naslovna_url, boja, domacin) uređuju vlasnik i izdavalac u panelu, na strani Brendirane stranice. boja je u obliku #RRGGBB (velika ili mala slova): jedna od šest ponuđenih ili boja koju je izdavalac sam izabrao. Uvek je dovoljno tamna da beli tekst na njoj bude čitljiv (kontrast najmanje 4.5:1), pa je možete koristiti za dugmad sa belim tekstom. Linkove ka mrežama gradite sami: https://instagram.com/{instagram}, https://facebook.com/{facebook}, https://www.tiktok.com/@{tiktok} i https://www.youtube.com/@{youtube}.kanali kaže da li je telefon na WhatsApp-u i Viberu: dugme prikažite samo za true (izdavalac to bira u panelu). Važi samo za taj telefon, ne za vaš broj za rezervacije. font je par fontova (naslovi / tekst, sve sa Google Fonts): jakarta (Plus Jakarta Sans), inter (Inter), montserrat (Montserrat), jost (Jost), playfair (Playfair Display / DM Sans), lora (Lora / Figtree), cormorant (Cormorant Garamond / Manrope) i fraunces (Fraunces / Karla); nepoznat ključ tretirajte kao null. pitanja su česta pitanja koja je izdavalac sam uneo (najviše 10; pitanje do 150, odgovor do 600 znakova, odgovor može imati nove redove); prikažite ih tim redom. ponude su reklamni baneri akcija koje danas važe (najviše 3; posle roka do više ne stižu): istaknuto je krupan tekst (npr. "-15%", do 14 znakova) ili null, slika je pozadina banera ili null, cta tekst dugmeta (null = "Rezerviši"), sablon raspored (slika, podeljeno, kupon, boja), pozadina (brend, tamna, svetla), odbrojavanje znači da prikažete "Još N dana" do roka, a animacija je izbor izdavaoca: nema, odsjaj, nalepnica ili dugme. apartman_adresa je apartman na koji vodi dugme (vaša stranica /a/<apartman_adresa>), ili null: tada dugme vodi na spisak apartmana domaćina. dolazak i odlazak su termin boravka akcije (ili null); kad postoje, prikažite ih na baneru i dugmetom otvorite rezervaciju sa tim datumima. Akcija čiji je termin u međuvremenu zauzet (bar jedna noć u kalendaru tog apartmana) se ne vraća, a vraća se sama kad se termin oslobodi. Link akcije je možda podeljen ranije, pa pre označavanja datuma u kalendaru proverite da su slobodni. To je samo najava: akcijsku cenu domaćin podešava kod apartmana, pa je cena u jedinicama i pri rezervaciji već tačna. razlika_bookingje izdavaočeva tvrdnja koliko je isti boravak skuplji na Bookingu (1 do 50, u procentima), npr. "Na Bookingu oko 15% skuplje"; null znači da je ne prikazujete. Sve vrednosti su čist tekst: prikazujte ih kao tekst, nikad kao HTML.

Stranice označite sa noindex i canonical ka punoj strani apartmana. Kad superadmin označi da vaš sajt ima ove rute, gost365.app/a/... i /i/... preusmeravaju na vaš domen.

POSThttps://gost365.app/api/v1/posete

Jedna poseta brendirane stranice na vašem sajtu (/a/ ili /i/), za statistiku vlasnika. Šaljite je iz serverske rute vašeg sajta (ključ ne sme u pregledač), jednom po otvaranju stranice, najbolje iz klijentske komponente koja zove vašu rutu (roboti koji ne izvršavaju JavaScript se tako ne broje).

{ "vrsta": "a", "adresa": "apartman-marko" }

204 uvek, bez tela. Adresa koja ne pripada vašem nalogu se tiho ignoriše.

POSThttps://gost365.app/api/v1/inquiries

Slanje kontakt upita sa sajta. Vlasniku objekta se šalje email obaveštenje.

{
  "name": "Jovan Jovanović",      // obavezno
  "email": "jovan@email.com",     // obavezno
  "message": "Da li je slobodno u julu?",  // obavezno
  "phone": "+381641234567",       // opciono
  "type": "kontakt"               // opciono, podrazumevano "kontakt"
}

201 { "success": true, "message": "Upit je uspešno primljen." }

400 Polja name, email i message su obavezna. · 500 Greška pri čuvanju upita.

GEThttps://gost365.app/api/v1/forms/{formId}

Dohvatanje definicije prilagođene forme koju je vlasnik napravio u Dashboard-u - koristite je da formu iscrtate na svom sajtu. Vraćaju se samo aktivne forme koje pripadaju vašem nalogu.

{
  "form": {
    "id": "3f2a1b0c-9d8e-7f6a-5b4c-3d2e1f0a9b8c",
    "name": "Zahtev za ponudu",
    "description": "Popunite za personalizovanu ponudu"
  },
  "fields": [
    {
      "label": "Broj osoba",
      "field_name": "broj_osoba",
      "field_type": "number",
      "placeholder": "npr. 4",
      "is_required": true,
      "options": null
    }
  ]
}

Polja stižu sortirana redosledom koji je vlasnik zadao. Polje options je niz stringova za polja tipa izbora, inače null.

404 Forma nije pronađena.

POSThttps://gost365.app/api/v1/submissions

Slanje popunjene forme. Pored form_id, sva ostala polja iz tela zahteva se čuvaju kao odgovori - koristite field_name vrednosti iz prethodnog endpointa kao ključeve.

{
  "form_id": "3f2a1b0c-9d8e-7f6a-5b4c-3d2e1f0a9b8c",  // obavezno
  "broj_osoba": "4",
  "napomena": "Dolazimo sa psom"
}

201 { "success": true, "message": "Upit je uspešno primljen." }

400 form_id je obavezan. ili Podaci forme su prazni. · 404 Forma nije pronađena ili nije aktivna.

GEThttps://gost365.app/api/ical/t/{token}

iCalendar (.ics) izvoz zauzetosti jedne jedinice, namenjen sinhronizaciji sa Booking.com, Airbnb i sličnim kanalima. Sadrži isključivo potvrđene rezervacije, sortirane po datumu početka, svaku kao celodnevni VEVENT.

Link sa tajnim tokenom vlasnik kopira u Dashboard-u (jedinica, "Sinhronizacija kalendara"). Token ne može da se izračuna ni pogodi i link radi uvek, i kada vlasnik isključi kalendar na sajtu za tu jedinicu.

Ovaj endpoint je izvan /api/v1/ i ne traži ni API ključ ni Origin: zaštita je sam token. Tretirajte link kao tajnu i ne objavljujte ga.

Stariji oblik https://gost365.app/api/ical/{unitId} i dalje radi, ali samo za jedinice čija se zauzetost prikazuje na sajtu, jer je ID jedinice javan u GET /units. Za jedinicu sa isključenim kalendarom vraća 404, kao da ne postoji. Kanale zato povezujte linkom sa tokenom.

Ako vlasnik za jedinicu isključi sinhronizaciju sa Airbnb/Booking, oba oblika linka vraćaju 404 dok je ponovo ne uključi, a uvoz sa kanala za tu jedinicu staje.

Odgovor je text/calendar; charset=utf-8 uz Content-Disposition: attachment i onemogućeno keširanje.

404 običan tekst Smeštajna jedinica nije pronađena. - vraća se i kada token ili unitId nisu ispravni. 500 Greška na serveru. Greške ovog endpointa nisu u JSON formatu.

Schema po Tipu Jedinice

Polje details u odgovoru API-ja sadrži specifične podatke za svaki tip jedinice. Polje billing_mode govori programeru koji UI prikazati korisniku - ono se izvodi iz tipa jedinice i ne može se posebno podesiti.

Sve vrednosti u details su stringovi - i one koje predstavljaju brojeve, jer se upisuju iz obrasca u Dashboard-u. Očekujte "max_guests": "4", a ne 4. Pre računanja ili poređenja konvertujte sa parseInt() odnosno parseFloat().
"nightly"Po noćenju

Apartman, Soba, Kuća

Date range picker - datum dolaska / odlaska

"daily"Po danu

Auto, Šator, Ostalo

Date range picker - datum preuzimanja / vraćanja

"hourly"Po satu

Kvad, Motor, Bicikl, Plovilo, Bazen, Sto, Očni pregled

Jedan datum + vremenski period (hh:mm), uvek sa vremenskom zonom

Smeštaj

Apartmanbilling_mode: "nightly"

Polja u details

PoljeTip
max_guestsstring (broj)
bedroomsstring (broj)
bedsstring (broj)
living_roomsstring (broj)
bathroomsstring (broj)
terracesstring (broj)
balconiesstring (broj)
parking_spotsstring (broj)
kitchen_typestring
area_sizestring (broj)
floorstring
total_floorsstring (broj)
house_rulesstring
checkin_instructionsstring

Amenities IDs (98)

wifiacheatingelevatorgarageoutdoor_parkingpoolpetssmokingsmoking_areafanfireplacesoundproofprivate_entranceev_chargerfridgestovemicrowavedishwashercoffeedishesovenfreezerkettletoasterblendercooking_basicswine_glassesshowerbathtubhairdryerwashertowelstoiletriesbidethot_waterslippersdryerbed_linenwardrobeextra_bedbaby_cotironblackoutextra_pillowstvsofadiningdeskstreamingcable_tvsound_systembooksboard_gamesgame_consolearmchairgymsaunahot_tuboutdoor_furnitureoutdoor_diningsun_loungersparasolfire_pitoutdoor_showerbeach_accesssea_viewlake_viewriver_viewmountain_viewcity_viewgarden_viewstreet_viewyard_viewhigh_chairkids_toysplaygroundbaby_bathchild_safetysmoke_detectorco_detectorfire_extinguisherfirst_aidsafecctvsmart_lockstep_freewheelchairgrab_railswide_doorsself_checkinluggage_storageairport_shuttlecleaninglong_staysbike_rentalhost_greetsbreakfast

Primer details objekta

{
  "max_guests": "4",
  "bedrooms": "2",
  "beds": "3",
  "living_rooms": "1",
  "bathrooms": "1",
  "terraces": "1",
  "balconies": "0",
  "parking_spots": "1",
  "kitchen_type": "open",
  "area_size": "65",
  "floor": "2. sprat",
  "total_floors": "4",
  "house_rules": "Tiho posle 22h. Ljubimci uz dogovor.",
  "checkin_instructions": "Ključ se uzima u kafiću u dvorištu."
}
Sobabilling_mode: "nightly"

Polja u details

PoljeTip
max_guestsstring (broj)
bedsstring (broj)
bathroomsstring (broj)
terracesstring (broj)
balconiesstring (broj)
parking_spotsstring (broj)
kitchen_typestring
area_sizestring (broj)
floorstring
total_floorsstring (broj)
house_rulesstring
checkin_instructionsstring

Amenities IDs (99)

wifiacheatingelevatorgarageoutdoor_parkingpoolpetssmokingsmoking_areafanfireplacesoundproofprivate_entranceev_chargerfridgestovemicrowavedishwashercoffeedishesovenfreezerkettletoasterblendercooking_basicswine_glassesshowerbathtubhairdryerwashertowelstoiletriesbidethot_waterslippersdryerbed_linenwardrobeextra_bedbaby_cotironblackoutextra_pillowstvsofadiningdeskstreamingcable_tvsound_systembooksboard_gamesgame_consolearmchairgymsaunahot_tuboutdoor_furnitureoutdoor_diningsun_loungersparasolfire_pitoutdoor_showerbeach_accesssea_viewlake_viewriver_viewmountain_viewcity_viewgarden_viewstreet_viewyard_viewhigh_chairkids_toysplaygroundbaby_bathchild_safetysmoke_detectorco_detectorfire_extinguisherfirst_aidsafecctvsmart_lockstep_freewheelchairgrab_railswide_doorsself_checkinluggage_storageairport_shuttlecleaninglong_staysbike_rentalhost_greetsbreakfastensuite

Primer details objekta

{
  "max_guests": "2",
  "beds": "2",
  "bathrooms": "1",
  "terraces": "0",
  "balconies": "1",
  "parking_spots": "0",
  "kitchen_type": "kitchenette",
  "area_size": "22",
  "floor": "3. sprat",
  "total_floors": "4",
  "house_rules": "Zabranjeno pušenje u sobi.",
  "checkin_instructions": "Prijava na recepciji, ulaz iz dvorišta."
}
Kućabilling_mode: "nightly"

Polja u details

PoljeTip
max_guestsstring (broj)
bedroomsstring (broj)
bedsstring (broj)
living_roomsstring (broj)
bathroomsstring (broj)
terracesstring (broj)
balconiesstring (broj)
parking_spotsstring (broj)
kitchen_typestring
area_sizestring (broj)
floorsstring (broj)
house_rulesstring
checkin_instructionsstring

Amenities IDs (99)

wifiacheatingelevatorgarageoutdoor_parkingpoolpetssmokingsmoking_areafanfireplacesoundproofprivate_entranceev_chargerfridgestovemicrowavedishwashercoffeedishesovenfreezerkettletoasterblendercooking_basicswine_glassesshowerbathtubhairdryerwashertowelstoiletriesbidethot_waterslippersdryerbed_linenwardrobeextra_bedbaby_cotironblackoutextra_pillowstvsofadiningdeskstreamingcable_tvsound_systembooksboard_gamesgame_consolearmchairgymsaunahot_tuboutdoor_furnitureoutdoor_diningsun_loungersparasolfire_pitoutdoor_showerbeach_accesssea_viewlake_viewriver_viewmountain_viewcity_viewgarden_viewstreet_viewyard_viewhigh_chairkids_toysplaygroundbaby_bathchild_safetysmoke_detectorco_detectorfire_extinguisherfirst_aidsafecctvsmart_lockstep_freewheelchairgrab_railswide_doorsself_checkinluggage_storageairport_shuttlecleaninglong_staysbike_rentalhost_greetsbbqgarden

Primer details objekta

{
  "max_guests": "8",
  "bedrooms": "3",
  "beds": "5",
  "living_rooms": "2",
  "bathrooms": "2",
  "terraces": "2",
  "balconies": "0",
  "parking_spots": "2",
  "kitchen_type": "separate",
  "area_size": "150",
  "floors": "2",
  "house_rules": "Roštilj samo na terasi. Depozit 100 EUR.",
  "checkin_instructions": "Šifra sefa sa ključem dolazi dan pre prijave."
}

Vozila

Kvadbilling_mode: "hourly"

Polja u details

PoljeTip
brand_modelstring
manufacture_yearstring (broj)
registration_platesstring
seatsstring (broj)
transmissionstring
fuel_typestring
engine_powerstring

Amenities IDs (6)

automaticmanual4x4helmetgpsinsurance

Primer details objekta

{
  "brand_model": "Yamaha Raptor 350",
  "manufacture_year": "2022",
  "registration_plates": "KV 123-AB",
  "seats": "1",
  "transmission": "manual",
  "fuel_type": "petrol",
  "engine_power": "350cc"
}
Motorbilling_mode: "hourly"

Polja u details

PoljeTip
brand_modelstring
manufacture_yearstring (broj)
registration_platesstring
transmissionstring
fuel_typestring
engine_powerstring

Amenities IDs (5)

automaticmanualhelmetgpsinsurance

Primer details objekta

{
  "brand_model": "Honda CB500F",
  "manufacture_year": "2021",
  "registration_plates": "BG 456-CD",
  "transmission": "manual",
  "fuel_type": "petrol",
  "engine_power": "500cc"
}
Biciklbilling_mode: "hourly"

Polja u details

PoljeTip
bike_typestring
frame_sizestring
speedsstring (broj)
manufacture_yearstring (broj)

Amenities IDs (5)

helmetlockbasketchild_seatelectric

Primer details objekta

{
  "bike_type": "ebike",
  "frame_size": "M / 48cm",
  "speeds": "9",
  "manufacture_year": "2023"
}
Plovilobilling_mode: "hourly"

Polja u details

PoljeTip
brand_modelstring
manufacture_yearstring (broj)
registration_platesstring
seatsstring (broj)
engine_powerstring
fuel_typestring

Amenities IDs (5)

life_jacketmotor_incgpsinsurancefishing

Primer details objekta

{
  "brand_model": "Fiart Mare 28",
  "manufacture_year": "2020",
  "registration_plates": "TV-4521",
  "seats": "6",
  "engine_power": "150 KS",
  "fuel_type": "petrol"
}
Autobilling_mode: "daily"

Polja u details

PoljeTip
brand_modelstring
manufacture_yearstring (broj)
registration_platesstring
seatsstring (broj)
transmissionstring
fuel_typestring
engine_powerstring

Amenities IDs (6)

automaticmanual4x4gpsinsurancechild_seat

Primer details objekta

{
  "seats": "5",
  "brand_model": "VW Golf 8",
  "manufacture_year": "2023",
  "registration_plates": "BG 789-EF",
  "transmission": "automatic",
  "fuel_type": "petrol",
  "engine_power": "130 KS"
}

Oprema

Bazenbilling_mode: "hourly"

Polja u details

PoljeTip
capacitystring (broj)
dimensionsstring
water_temperaturestring (broj)

Amenities IDs (6)

heatingoutdoorindoorsaunachanging_roomtowels

Primer details objekta

{
  "capacity": "20",
  "dimensions": "25×10×1.8m",
  "water_temperature": "28"
}
Šatorbilling_mode: "daily"

Polja u details

PoljeTip
capacitystring (broj)
dimensionsstring
equipmentstring

Amenities IDs (6)

installationoutdoorindoorpowersafety_nettables_chairs

Primer details objekta

{
  "capacity": "100",
  "dimensions": "10×20m",
  "equipment": "Stolice, stolovi, LED rasveta, pozornica"
}
Ostalobilling_mode: "daily"

Polja u details

PoljeTip
capacitystring (broj)
dimensionsstring
equipmentstring

Amenities IDs (5)

installationoutdoorindoorpowersafety_net

Primer details objekta

{
  "capacity": "50",
  "dimensions": "200m²",
  "equipment": "Projektor, platno, PA sistem"
}

Ugostiteljstvo

Stobilling_mode: "hourly"

Polja u details

PoljeTip
capacitystring (broj)
min_guestsstring (broj)
zonestring
notesstring

Amenities IDs (7)

terasavipindoorparkingdisabledmusicbirthday

Primer details objekta

{
  "capacity": "4",
  "min_guests": "1",
  "zone": "basta",
  "notes": "Sto je pogodan za proslave."
}

Zajednički ključevi u details (svi tipovi)

KljučTipOpis
locationobjectStrukturirana adresa: street, city, postalCode, country - sva podpolja su stringovi. Postoji samo ako je vlasnik popunio adresu. Isti podatak u spljoštenom obliku stiže i kao top-level address.
service_optionsarrayLista usluga sa fiksnom cenom: [{ "name": "...", "price": 25 }]. Ovde je price pravi broj. Ako je prisutno, uz rezervaciju šaljite service_type.
notification_soundstring | nullInterno podešavanje Dashboard-a. Zanemarite ga u integraciji.

Polja u details su opciona i details je slobodan JSON objekat - ne pretpostavljajte fiksan skup ključeva. Ključ koji vlasnik nikad nije popunio uopšte ne postoji u objektu (dobijate undefined, ne null), a polje koje je popunjeno pa obrisano, kao i izbor vraćen na praznu opciju, čuva se kao prazan string. Zato proveravajte i nepostojanje ključa i prazan string, npr. if (details.transmission) { ... }.

Implementacija u Kodu

Kopirajte gotove kodne primere za vašu platformu i zamenite placeholder API ključ svojim.

// 1. Dobijanje rezervacionih jedinica i zauzetih termina
// Origin zaglavlje browser dodaje sam kod poziva na drugi domen.
const fetchUnits = async () => {
  const response = await fetch('https://gost365.app/api/v1/units', {
    method: 'GET',
    headers: {
      'x-api-key': 'gost365_live_vaš_api_ključ_ovde',
      'Content-Type': 'application/json'
    }
  });

  if (!response.ok) {
    // Razlog greške je uvek u polju "error" u telu odgovora, ne u statusText.
    const errorData = await response.json();
    throw new Error(errorData.error || 'Greška pri učitavanju jedinica');
  }

  const result = await response.json();
  console.log(result.data); // Niz jedinica

  // Zauzeti termini za prvu jedinicu (potvrđene i na čekanju):
  const zauzeti = result.data[0].availability_blocks.map(b => ({
    od: String(b.start_date).split('T')[0],
    do: String(b.end_date).split('T')[0]
  }));
  console.log(zauzeti);
};
// 2. Slanje novog upita za rezervaciju sa sajta
const createBooking = async (bookingData) => {
  const response = await fetch('https://gost365.app/api/v1/bookings', {
    method: 'POST',
    headers: {
      'x-api-key': 'gost365_live_vaš_api_ključ_ovde',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      unit_id: "8f7e6d5c-4b3a-2a19-0e9d-8c7b6a5f4e3d", // UUID jedinice
      start_date: "2026-07-01",
      end_date: "2026-07-08",
      guest_name: "Jovan Jovanović",
      guest_email: "jovan.jovanovic@email.com",
      guest_phone: "+381641234567", // opciono
      adults_count: 2, // opciono, podrazumevano 1
      children_count: 1, // opciono, podrazumevano 0
      infants_count: 0, // opciono, podrazumevano 0
      notes: "Molimo za tihu sobu i krevetac za bebu." // opciono
    })
  });

  if (response.status === 409) {
    // Pažnja: 409 je moguć i za termin koji na vašem kalendaru izgleda slobodno,
    // jer se provera radi i protiv rezervacija na čekanju koje GET /units ne vraća.
    alert('Izabrani termin je već zauzet. Molimo izaberite drugi.');
    return;
  }

  if (!response.ok) {
    const errorData = await response.json();
    throw new Error(errorData.error || 'Neuspešno slanje rezervacije');
  }

  const result = await response.json();
  // Rezervacija je kreirana sa statusom "pending" - vlasniku je poslat email za potvrdu.
  console.log('Zahtev poslat! ID rezervacije:', result.booking_id);
};