# API usate ed esposte Questo documento descrive le API esterne che cinema-watch legge e le API che l'app espone. Le trappole dei dati (chiave di uno spettacolo, giorno del cinema, posti `BLOCCATO`, asse delle file) sono in [MEMORY.md](MEMORY.md) e qui non si ripetono. ## API esterne Sono pubbliche, senza login, e non sono ufficiali: i circuiti possono cambiarle senza preavviso. Le risposte si validano con zod; un campo che manca o ha il tipo sbagliato scarta quell'elemento o dà un errore tipizzato, mai un arresto dell'app. ### Backend UCI Base: `https://myuci---uci-backend-production-nfluwp7wga-oc.a.run.app/api`. Le richieste portano l'header `Referer: https://ucicinemas.it`. Tutte rispondono con `{ "data": ... }`. | Chiamata | A cosa serve | Campi usati | |---|---|---| | `GET /theatres` | Elenco dei cinema | `slug`, `name`, `city`, `province`, `region`, `is_luxe`, `screens` (formati di sala del cinema) | | `GET /theatres/{slug}/programming/{AAAA-MM-GG}` | Programmazione di un giorno | vedi sotto | | `GET /theatres/{slug}/programming` | Programmazione di tutti i giorni, circa 2 MB: `data` è un elenco di oggetti `{ giorno: [film] }`. Serve solo alla UI | come sopra | | `GET /movies` | Catalogo di tutti i film, circa 450 KB. Serve alla pagina Cerca, con una cache di un'ora | `slug`, `title`, `poster`, `first_performance`, `last_performance`, `screens[].name` | | `GET /movies/{slug}` | Dettaglio di un film | `slug`, `title`, `poster` | | `GET /movies/{slug}/theatres` | Cinema in cui il film è in programma | `slug`, `name`, `movie_variants` | Ogni film della programmazione porta `slug`, `title`, `poster` e `screens`, un elenco di oggetti che hanno come chiave il formato (`IMAX`, `XL - INFINITY VISION`, `2D`) e come valore le versioni. Una versione ha: - `language.name` e `duration` (`HH:MM`); - `performances`, con `day` (giorno del cinema), `starts_at` (`AAAA-MM-GG HH:MM:SS`), `actual_start_at` (`HH:MM`), `room` e `cart_link`. Da `cart_link` (`/movies/{film}/acquista/{LocalId}/{EventId}/{PerformanceId}`) l'app ricava i tre identificativi e il link di acquisto su `ucicinemas.it`. Uno spettacolo senza un `cart_link` in questo formato si scarta. Gli slug ammessi sono `[a-z0-9-]` e i giorni sono date ISO: altro non parte come richiesta. ### Proxy Webtic Base: `https://uci-website-webtic-proxy-production-1042268733238.europe-west8.run.app/Api2`. Le richieste sono `POST` con corpo JSON e portano `Origin: https://ucicinemas.it` e `Referer: https://ucicinemas.it/`. Gli identificativi devono essere interi positivi. Ogni risposta ha la forma `{ "Status": { "Success", "Message", "ReasonCode" }, "Data": ... }`; con `Success` falso l'app lo tratta come errore e ne riporta il `ReasonCode`. | Chiamata | Corpo | Campi usati | |---|---|---| | `POST /Performance` | `{ LocalId, PerformanceId }` | `Performance` (`LocalId`, `EventId`, `PerformanceId`, `ScreenId`, `ScreenName`, `StartTime`), `Event.Title`, e dai prezzi in `Tickets.ShoppingModes[].PaymentModes[].SectorPrices[]` il minimo fra `PriceStartFrom` o, se manca, `Prices[].TotalPrice` | | `POST /Screen` | `{ LocalId, ScreenId }` | `Screen.Seats[]` con `SeatId` (per esempio `F/12`), `SeatType`, `SectorId`, `Row`, `Column` | | `POST /Occupancy` | `{ LocalId, PerformanceId }` | `Occupancy.Seats[]` con `SeatId` e `SeatStatus`: `ACQUISTO` è venduto, `BLOCCATO` è nel carrello, qualunque altro valore conta come preso | La pianta di una sala (`/Screen`) cambia di rado: l'app la tiene in `screens/` per 7 giorni e la scarica prima se l'occupazione contiene posti che la pianta non ha. Il proxy risponde solo per i cinema UCI: per un'altra sede dà `Unauthorized` (`ReasonCode` 1). ### Webtic diretto Base: `https://secure.webtic.it/api/wtjsonservices.ashx`, con il metodo nel parametro `wtid`. Serve ai cinema che non sono UCI (Notorious, Cinelandia, Movie Planet e gli indipendenti). Nessun header particolare. Ogni risposta ha la forma `{ "userdata": { "StatusOk", "ReasonCode", "Message" }, "DS": ... }`; con `StatusOk` falso l'app lo tratta come errore. Una sede chiusa risponde con `StatusOk` falso dopo circa 12 secondi. | Chiamata | A cosa serve | Campi usati | |---|---|---| | `GET ?wtid=getLocalsTitles` | Tutte le 1171 sedi, circa 1,3 MB | `LocalsTitles.Locals` (`LocalId`, `Description`, `Town`, `District`, `DistrictAbbreviation`, `Latitude`, `Longitude`) e `LocalsTitles.Titles[]` (`Category`, `Locals[].LocalId`) | | `GET ?wtid=getFullScheduling&localid=N` | Programmazione di tutti i giorni di una sede, circa 100 KB | `Scheduling.LocalId` e `Events[]` con `EventId`, `Title`, `Type` (`CINEMA` per i film), `Is3D`, `Duration`, `Picture`, `EventProperties[]` (`EventProperty`, `EventPropertyCategory`: `SCREEN`, `LANGUAGE`), `Days[]` con `Day` (giorno del cinema) e `Performances[]` (`PerformanceId`, `Time`, `ScreenName`, `StartTime`) | | `GET ?wtid=getPerformanceInfo&localid=N&performanceid=P` | Spettacolo, senza prezzi | `Performance` (`LocalId`, `EventId`, `PerformanceId`, `ScreenId`, `Screen`, `StartTime`, `Title`) | | `POST ?wtid=getMapSeats` con `{ data: { LocalId, ScreenId } }` | Pianta | `MapSeats.posti[]` con `idposto`, `fila`, `colonna`, `settore`, `tipologia`: gli stessi valori di `SeatId`, `Row`, `Column`, `SectorId` e `SeatType` del proxy | | `POST ?wtid=getOccupancy` con `{ data: { LocalId, PerformanceId } }` | Posti presi | `Occupancy.posti[]` con `idposto` e `status` (`ACQUISTO`, `BLOCCATO`) | Le locandine sono `https://secure.webtic.it/api/` seguito dal `Picture` relativo. Il link "Acquista" è `https://secure.webtic.it/angwt/webtic.aspx?lid={LocalId}&lng=it#/shoppingmode/it/0/{LocalId}/{EventId}/{PerformanceId}`: apre il login di Webtic e poi lo spettacolo. ### The Space Base: `https://www.thespacecinema.it/api/microservice`. Senza token quasi tutte le chiamate rispondono 401. Le risposte hanno la forma `{ "result": ..., "responseCode", "errorMessage" }`. | Chiamata | A cosa serve | Campi usati | |---|---|---| | `POST /auth/token` | Token ospite, senza login | i cookie della risposta: `microservicesToken` (12 ore), `microservicesRefreshToken` e `__cflb` di Cloudflare | | `GET /showings/cinemas` | Elenco dei 35 cinema | `cinemaId`, `cinemaName`, `whatsOnUrl` (la pagina del cinema) | | `GET /showings/cinemas/{id}/films?minEmbargoLevel=1&includesSession=true` | Programmazione di tutti i giorni di un cinema, 450-600 KB | `filmId`, `filmTitle`, `posterImageSrc`, `showingGroups[]` con `date` (giorno del cinema) e `sessions[]` (`sessionId`, `startTime`, `screenName`, `duration`, `bookingUrl`, `attributes[]`) | | `GET /showings/films` | Catalogo dei film, per la pagina Cerca, con una cache di un'ora | `filmId`, `filmTitle`, `posterImageSrc`, `releaseDate`, `showingInCinemas` | | `GET /booking/Session/{cinemaId}/{sessionId}/seats` | Pianta e posti presi in una chiamata | `seatingData.screenLabel`, `seatRows[]` (`rowLabel`, `columns[]` con `name`, `seatStatus`, `areaCategoryCode`), `areaCategories[]` (`areaCategoryCode`, `areaMainPriceInCents`) | Gli `attributes` danno il formato (`Session` "2D"; `Session_Special` "3D", "INFINITY VISION", "ANTEPRIMA") e la lingua (`Language` "ITALIANO" o "LINGUA ORIGINALE"). Coordinate e provincia di un cinema vengono dalla sua pagina HTML: `q=lat, lon` nel link della mappa e la sigla in fondo a `
`. Le locandine sono su `www.thespacecinema.it`. Il link "Acquista" è `https://www.thespacecinema.it/prenotare-il-biglietto/summary/{cinemaId}/{filmId}/{sessionId}`. ### 18Tickets Ogni cinema ha un dominio suo, `