# ZnajdzPlaceID API — dokumentacja

- **Wersja:** 2.0.0
- **Ostatnia aktualizacja:** 2026-10-07
- **Adres bazowy:** `https://znajdzplaceid.pl/api/`
- **Ta dokumentacja jako Markdown:** `https://znajdzplaceid.pl/api-docs.php?format=md`
- **Specyfikacja maszynowa (OpenAPI 3.0):** `https://znajdzplaceid.pl/api-docs/openapi.yaml`

API zwraca dane wizytówek Google Maps: wyszukiwanie Place ID, szczegóły wizytówki, listy opinii
(z sortowaniem i filtrem gwiazdek), zdjęcia, profile autorów opinii i sprawdzanie, czy konkretna
opinia jest publicznie widoczna. Nie korzysta z oficjalnego Google Places API — dane pochodzą
z tych samych źródeł co strona Map.

---

## Spis treści

1. [Informacje ogólne](#1-informacje-ogólne)
2. [Identyfikatory miejsc](#2-identyfikatory-miejsc)
3. [Szybki start — typowe scenariusze](#3-szybki-start--typowe-scenariusze)
4. [Endpointy GET](#4-endpointy-get)
   - [/search](#search) · [/details](#details) · [/all](#all)
   - [/place-reviews](#place-reviews) · [/review-job](#review-job)
   - [/place-photos](#place-photos) · [/user-profile](#user-profile)
   - [/review-visibility](#review-visibility) · [/review-details](#review-details) · [/info](#info)
5. [Endpointy POST](#5-endpointy-post) — `/convert`, `/batch`, `/parse-*`
6. [Obiekt opinii](#6-obiekt-opinii)
7. [Obiekt wizytówki (`details`)](#7-obiekt-wizytówki-details)
8. [Błędy i limity](#8-błędy-i-limity)
9. [Znane ograniczenia](#9-znane-ograniczenia)
10. [Historia zmian](#10-historia-zmian)

---

## 1. Informacje ogólne

### Wywołanie

Każdy endpoint jest dostępny w dwóch równoważnych postaciach:

```
GET https://znajdzplaceid.pl/api/details?placeid=ChIJ...
GET https://znajdzplaceid.pl/api/?endpoint=details&placeid=ChIJ...
```

Parametry GET przekazuje się w query stringu (kodowanie URL, UTF-8). Endpointy POST przyjmują
ciało `application/json`.

### Uwierzytelnianie

Klucz API jest obecnie **opcjonalny**. Jeśli został przydzielony, przekaż go jednym ze sposobów:

- nagłówek `Authorization: Bearer <API_KEY>`
- parametr `?api_key=<API_KEY>`

Klucz rozpoznany przez serwer daje osobny licznik limitu (zamiast liczenia po adresie IP).

### Format odpowiedzi

Zawsze JSON (UTF-8). Sukces:

```json
{
  "success": true,
  "data": { "...": "..." },
  "timestamp": "2026-10-07T12:55:10+00:00",
  "execution_time": "537.8ms"
}
```

Błąd:

```json
{
  "success": false,
  "error": {
    "message": "Nie znaleziono Place ID dla podanego zapytania",
    "code": 400,
    "timestamp": "2026-10-07T12:56:15+00:00"
  },
  "execution_time": "800.69ms"
}
```

Kod HTTP odpowiedzi odpowiada polu `error.code`. Niektóre odpowiedzi zawierają dodatkowe pole
techniczne `proxy_ip` — służy do diagnostyki, klient może je ignorować.

### Konwencje

- Nazwy pól: endpointy `details`, `batch` i `place-photos` używają **polskich** nazw pól
  (`nazwa_wizytowki`, `srednia_ocen`…). Endpointy `all`, `search`, `place-reviews` i
  `user-profile` — **angielskich**. To celowe (zgodność wsteczna) i nie zmieni się.
- Daty: ISO 8601 w UTC (`2026-09-06T12:47:31.098Z`) albo `RRRR-MM-DD`. Pola `*_text` / `*_tekst`
  to tekst wyświetlany przez Google po polsku („miesiąc temu”).
- Brak danych = `null` (nigdy pusty string w miejsce liczby). Listy bez elementów = `[]`.
- Google zwraca treści w języku polskim (`hl=pl`).

---

## 2. Identyfikatory miejsc

| Format | Przykład | Gdzie przyjmowany |
|---|---|---|
| **Place ID** (ChIJ) | `ChIJBQyGzddtFkcRLnmqsFHqdYU` | wszędzie, gdzie jest `placeid` |
| **fid / feature ID** (hex) | `0x47166dd7cd860c05:0x8575ea51b0aa792e` | `placeid` w `details`, `place-reviews`, `place-photos` |
| **CID** (dziesiętny) | `9616850215896774958` | `search`, `all`, `POST /convert` |
| **URL Map** | `https://maps.app.goo.gl/...`, `https://www.google.com/maps/place/...` | `search`, `all` |
| **Nazwa** (tekst) | `Qultova Myślenice` | `search`, `all` |

CID to druga połowa fid zapisana dziesiętnie. Wszystkie formaty da się przeliczyć przez
`/search` (zwraca `place_id`, `fid` i `cid` naraz) albo `POST /convert`.

---

## 3. Szybki start — typowe scenariusze

**Znajdź wizytówkę po nazwie:**
```
GET /api/search?query=Qultova Myślenice
```

**Negatywne opinie wizytówki (1–3★), od najgorszych:**
```
GET /api/place-reviews?placeid=ChIJBQyGzddtFkcRLnmqsFHqdYU&max_stars=3&count=100
```
Przy `max_stars` ≤ 3 API samo sortuje od najniższych ocen i kończy pobieranie, gdy dalej są już
tylko lepsze oceny — dostajesz komplet negatywnych w 1–3 s.

**Tylko nowe opinie od ostatniego sprawdzenia:**
```
GET /api/place-reviews?placeid=...&sort=najnowsze&count=50
```
i odrzuć po swojej stronie opinie starsze niż poprzedni odczyt (porównuj `date_created`).

**Pełna lista dużej wizytówki (setki/tysiące opinii) — tryb asynchroniczny:**
```
GET /api/place-reviews?placeid=...&count=5000&sort=najnowsze&async=1   → job_id
GET /api/review-job?id=<job_id>                                        → stan
GET /api/review-job?id=<job_id>&wynik=1                                → opinie
```

**Czy konkretna opinia jest widoczna publicznie:**
```
GET /api/review-visibility?url=https://www.google.com/maps/contrib/<ID autora>/place/<Place ID>/
```

---

## 4. Endpointy GET

### /search

Zamienia nazwę, URL, CID albo Place ID na Place ID. Przy wyszukiwaniu po nazwie zwraca też dane
najlepszego trafienia i do 4 alternatyw — **zawsze sprawdź `name`/`address`**, zanim użyjesz
wyniku (dla nazw sieciowych pierwszy wynik może być inną placówką).

`GET /api/search?query=<tekst>`

| Parametr | Typ | Wymagany | Opis |
|---|---|---|---|
| `query` | string | tak | Nazwa (najlepiej z miastem), URL Map, CID albo Place ID |

Pole `type` mówi, co rozpoznano: `search_result` (wyszukanie po nazwie/URL), `cid_converted`
(podano CID), `direct_place_id` (podano Place ID — zwracany bez sprawdzania).

```json
{
  "success": true,
  "data": {
    "query": "Qultova Myślenice",
    "place_id": "ChIJBQyGzddtFkcRLnmqsFHqdYU",
    "type": "search_result",
    "name": "Qultova | Restauracja & Bar",
    "address": "Zdrojowa 1, 32-400 Myślenice",
    "fid": "0x47166dd7cd860c05:0x8575ea51b0aa792e",
    "cid": "9616850215896774958",
    "categories": ["Restauracja", "Bar", "Pizzeria"],
    "rating": 4.5,
    "alternatives": [
      { "place_id": "ChIJ...", "name": "...", "address": "..." }
    ]
  }
}
```

Pola `name`, `address`, `fid`, `cid`, `categories`, `rating`, `alternatives` występują przy
`type = search_result` dla wyszukiwania po nazwie. Czas: ~0,5–1 s. Brak wyniku → HTTP 400.

---

### /details

Pełne dane wizytówki (≈60 pól) w jednym żądaniu, ~0,5–1 s.

`GET /api/details?placeid=<Place ID lub fid>`

| Parametr | Typ | Wymagany | Opis |
|---|---|---|---|
| `placeid` | string | tak | `ChIJ...` albo `0x...:0x...` |
| `uslugi` | 0/1 | nie | `1` = dołącz cennik usług/menu (`uslugi`). Domyślnie wyłączone — blok potrafi mieć 50 KB |

```json
{
  "success": true,
  "data": {
    "place_id": "ChIJBQyGzddtFkcRLnmqsFHqdYU",
    "details": { "nazwa_wizytowki": "Qultova | Restauracja & Bar", "srednia_ocen": 4.5, "...": "..." },
    "uslugi": { "liczba_pozycji": 102, "liczba_kategorii": 22, "pozycje": [ ] }
  }
}
```

Opis wszystkich pól `details`: [rozdział 7](#7-obiekt-wizytówki-details). Nieistniejący lub
usunięty Place ID → HTTP 400 „Nie znaleziono miejsca dla podanego Place ID”.

---

### /all

Jedno wywołanie dla dowolnego wejścia (URL, nazwa, CID, Place ID): rozpoznaje miejsce i zwraca
jego dane pod **angielskimi** nazwami pól.

`GET /api/all?url=<URL | nazwa | CID | Place ID>`

```json
{
  "success": true,
  "data": {
    "query": "Qultova Myślenice",
    "place_id": "ChIJBQyGzddtFkcRLnmqsFHqdYU",
    "source": "search_result",
    "cid": "9616850215896774958",
    "maps_url": "https://www.google.com/maps/place/?q=place_id:ChIJBQyGzddtFkcRLnmqsFHqdYU",
    "maps_cid_url": "https://www.google.com/maps?cid=9616850215896774958",
    "details": {
      "name": "Qultova | Restauracja & Bar",
      "address": "Zdrojowa 1, 32-400 Myślenice",
      "category": "Restauracja",
      "categories": ["Restauracja", "Bar"],
      "subtypes": ["restaurant", "bar"],
      "rating": 4.5,
      "reviews_count": 282,
      "rating_breakdown": { "5_stars": 230, "4_stars": 10, "3_stars": 13, "2_stars": 11, "1_star": 18 },
      "cid": "9616850215896774958",
      "business_status": "otwarte",
      "claimed": true,
      "manager_id": "108146735889097402943",
      "website": "https://qultova.pl/",
      "phone": "511 335 417",
      "phone_raw": "511335417",
      "hours": [ { "dzien": "środa", "godziny": ["12:00–21:00"] } ],
      "description": "...",
      "has_description": true,
      "attributes": [ { "grupa": "Dostępne opcje", "pozycje": ["Ogródek", "Na wynos"] } ],
      "also_searched": [ { "nazwa": "...", "ocena": 4.9, "liczba_opinii": 844, "odleglosc_m": 1894 } ],
      "photos_preview_count": 4,
      "photo_thumbnails": ["https://lh3.googleusercontent.com/..."],
      "entity_id": "/g/11z5w5pv87",
      "street": "Zdrojowa 1",
      "postal_code": "32-400",
      "city": "Myślenice",
      "lat": 49.8216022,
      "lng": 19.9463146,
      "source": "place_details_parser"
    }
  }
}
```

Znaczenie pól jak w [rozdziale 7](#7-obiekt-wizytówki-details) (`business_status` =
`status_dzialalnosci`, `claimed` = `przejeta`, `manager_id` = `zarzadca_id`,
`also_searched` = `inni_wyszukiwali`). `source` w `data`: `search_result` (nazwa lub URL) | `direct_place_id` | `direct_cid`.

---

### /place-reviews

Lista opinii wizytówki z sortowaniem, filtrem gwiazdek i paginacją po stronie serwera.

`GET /api/place-reviews?placeid=<ID>[&count=N][&sort=...][&min_stars=X][&max_stars=Y][&async=1]`

| Parametr | Typ | Domyślnie | Opis |
|---|---|---|---|
| `placeid` | string | — | **Wymagany.** `ChIJ...` albo `0x...:0x...` |
| `count` | int 1–10000 | 10 | Ile opinii pobrać (górna granica — mniej, jeśli wizytówka ma mniej) |
| `max_pages` | int 1–100 | 1 | Starszy odpowiednik `count`: liczba opinii = `max_pages × 10`. Ignorowany, gdy podano `count` |
| `sort` | string | `trafnosc` | Kolejność — patrz niżej |
| `min_stars` | int 1–5 | — | Zwróć tylko opinie z oceną ≥ X |
| `max_stars` | int 1–5 | — | Zwróć tylko opinie z oceną ≤ Y |
| `async` | 0/1 | 0 | `1` = tryb asynchroniczny (zalecany od ~500 opinii), patrz [/review-job](#review-job) |

**Wartości `sort`:**

| Wartość | Alias | Kolejność |
|---|---|---|
| `trafnosc` | `relevance` | Jak domyślnie na wizytówce w Mapach (wybór Google) |
| `najnowsze` | `newest` | Od ostatniej aktywności. Opinia edytowana trafia na początek — do pobierania przyrostowego |
| `najnizsze` | `lowest` | Od najniższej oceny (1★ najpierw) |
| `najwyzsze` | `highest` | Od najwyższej oceny (5★ najpierw) |

Nieznana wartość `sort` jest traktowana jak `trafnosc` (bez błędu).

**Automatyczne sortowanie:** gdy podasz `max_stars` ≤ 3 i **nie podasz** `sort`, API użyje
`najnizsze` i zwróci `"sort_auto": true`. Przy kolejności „trafność” negatywne opinie giną wśród
pozytywnych (przykład: wizytówka z 15 tys. opinii — 1 negatywna wśród pierwszych 30 według
trafności, 30 z 30 przy `najnizsze`). Żeby wymusić trafność, podaj jawnie `sort=trafnosc`.

Przy sortowaniu po ocenie serwer kończy paginację, gdy dalsze strony zawierają już tylko oceny
spoza zakresu `min_stars`/`max_stars`, więc `count` można ustawić z zapasem bez kosztu czasu.

**Odpowiedź:**

```json
{
  "success": true,
  "data": {
    "place_id": "ChIJBQyGzddtFkcRLnmqsFHqdYU",
    "place_info": {
      "name": "Qultova | Restauracja & Bar",
      "address": "Zdrojowa 1, 32-400 Myślenice",
      "rating": 4.5,
      "reviews_count": 282
    },
    "sort": "najnizsze",
    "sort_auto": true,
    "reviews": [ { "...": "obiekt opinii, rozdział 6" } ],
    "total_fetched": 42,
    "error": null,
    "performance": {
      "execution_time": 2.41,
      "reviews_returned": 42,
      "service_ms": 1850,
      "zrodlo": "boq"
    }
  }
}
```

`total_fetched` = liczba opinii w `reviews` **po** filtrze gwiazdek. `reviews_count` w
`place_info` = całkowita liczba opinii wizytówki według Google.

**Czas:** 10 opinii ~1 s; 100 opinii ~2–3 s; kolejne strony po ~0,3–0,6 s (pierwsza strona to
10 opinii, następne po 20). Dla kilkuset i więcej opinii używaj `async=1` — połączenie
synchroniczne trzyma klienta przez cały czas pobierania.

**Kompletność:** sortowanie `trafnosc` pomija część opinii bez tekstu (tak samo jak widok Map —
ok. 10–12%). Pełną listę daje `sort=najnowsze`.

---

### /review-job

Odbiór zadania zgłoszonego przez `place-reviews&async=1`.

**Zgłoszenie** (odpowiada od razu, ~0,5–1 s):

```
GET /api/place-reviews?placeid=<ID>&count=5000&sort=najnowsze&async=1
```
```json
{
  "success": true,
  "data": {
    "place_id": "ChIJ...",
    "place_info": { "name": "...", "address": "...", "rating": 4.8, "reviews_count": 1173 },
    "async": true,
    "job_id": "6c59b407-9ed4-4741-8ce2-40a187857429",
    "stan": "oczekuje",
    "w_kolejce": 1,
    "szacowany_czas_s": 360,
    "sort": "najnowsze",
    "sort_auto": false,
    "odbior": "/api/review-job?id=6c59b407-9ed4-4741-8ce2-40a187857429&wynik=1"
  }
}
```

**Stan i wynik:**

`GET /api/review-job?id=<job_id>[&wynik=1]`

| Parametr | Opis |
|---|---|
| `id` | **Wymagany.** `job_id` (UUID) |
| `wynik` | `1` = dołącz opinie (tylko gdy `stan = gotowe`). Bez niego odpowiedź jest mała — odpytuj co 3–10 s |

```json
{
  "success": true,
  "data": {
    "job_id": "6c59b407-...",
    "stan": "gotowe",
    "gotowe": true,
    "total_fetched": 32,
    "pages": 3,
    "elapsed_ms": 1912,
    "error": null,
    "reviews": [ ]
  }
}
```

- `stan`: `oczekuje` → `trwa` → `gotowe` albo `blad` (wtedy powód w `error`).
- Filtr `min_stars`/`max_stars` z zgłoszenia jest stosowany do wyniku (`total_fetched` = po filtrze).
- Wynik jest przechowywany **6 godzin**; potem HTTP 404.
- Zadania przerwane restartem serwisu wznawiają się automatycznie.
- Naraz wykonują się 2 zadania; kolejka do 50 — przy pełnej kolejce zgłoszenie dostaje HTTP 429
  (ponów po chwili).
- `job_id` jest jedynym zabezpieczeniem wyniku — nie udostępniaj go publicznie.

---

### /place-photos

Zdjęcia wizytówki z rozbiciem na właściciela i klientów, źródłem (urządzeniem) i statystykami
aktywności właściciela. ~0,4–1 s.

`GET /api/place-photos?placeid=<ID>[&count=N]`

| Parametr | Typ | Domyślnie | Opis |
|---|---|---|---|
| `placeid` | string | — | **Wymagany.** `ChIJ...` albo `0x...:0x...` |
| `count` | int 1–195 | 20 | Ile zdjęć zwrócić w liście `zdjecia` (statystyki zawsze liczone z pełnej strony 195) |

| Pole | Opis |
|---|---|
| `fid` | Feature ID wizytówki |
| `zdjecia_licznik_google` | Liczba zdjęć według Google; dla dużych wizytówek obcinana ok. 200 |
| `zdjecia_licznik_google_uciety` | `true` = licznik powyżej jest dolnym ograniczeniem |
| `zdjecia_wlasciciela` | Liczba zdjęć dodanych przez właściciela |
| `zdjecia_wlasciciela_niepelne` | `true` = właściciel ma ≥195 zdjęć, liczba jest dolnym ograniczeniem |
| `ostatnie_zdjecie_wlasciciela` / `_dni_temu` | Data ostatniego zdjęcia właściciela i ile dni temu |
| `pierwsze_zdjecie_wlasciciela` | Data pierwszego zdjęcia właściciela |
| `klienci_mobile` / `klienci_desktop` / `klienci_desktop_procent` | Zdjęcia klientów wg urządzenia |
| `klienci_android` / `klienci_ios` | Zdjęcia klientów wg systemu |
| `statystyki` | Obiekt: `wlasciciel` {`liczba`, `pierwsze`, `ostatnie`, `dni_od_ostatniego`, `w_ostatnim_roku`, `zaniedbana`}, `klienci` {`liczba`, `ostatnie`, `dni_od_ostatniego`, `w_ostatnim_roku`, `w_ostatnich_90_dniach`, `mobile`, `desktop`, `android`, `ios`}, `udzial_wlasciciela_procent`, `na_lata` {rok: {`wlasciciel`, `klienci`}}, `rozdzielczosc_mediana_mpx`, `slabej_jakosci` |
| `zdjecia` | Lista zdjęć (do `count`) |
| `zdjecia_wlasciciela_lista` | Lista zdjęć właściciela |

Element listy zdjęć:

```json
{
  "id": "CIABIhBtelQPmF1uoSQ5H6DQddtB",
  "url": "https://lh3.googleusercontent.com/...",
  "szerokosc": 8192,
  "wysokosc": 5464,
  "od_wlasciciela": true,
  "zrodlo": "bizbuilder:gmb_on_local_universal_desktop",
  "urzadzenie": "desktop",
  "system": null,
  "data": "2026-05-04"
}
```

`urzadzenie`: `mobile` | `desktop` | `null`; `system`: `android` | `ios` | `null`.
Uwaga: udział zdjęć z desktopu zależy głównie od wieku zdjęć (starsze częściej z komputera),
nie od autentyczności.

---

### /user-profile

Profil autora opinii (kontrybutora Map). ~0,5 s.

`GET /api/user-profile?id=<ID autora>`

| Parametr | Typ | Opis |
|---|---|---|
| `id` | string 10–25 cyfr | **Wymagany.** ID autora — pole `author_id` z opinii albo liczba z `/maps/contrib/<ID>` |

| Pole | Opis |
|---|---|
| `username`, `profile_photo`, `profile_description`, `profile_url` | Dane publiczne profilu |
| `user_id`, `google_user_id` | ID autora |
| `local_guide_level`, `local_guide_points`, `local_guide_next_level_text` | Program Local Guides |
| `review_count` / `review_count_visible` | Opinie: wszystkie / widoczne publicznie |
| `rating_count` / `rating_count_visible` | Same oceny (bez tekstu): wszystkie / widoczne |
| `photo_count`, `video_count`, `caption_count`, `answer_count`, `edit_count`, `reported_count`, `place_added_count`, `road_added_count`, `verified_count`, `qa_count` | Liczniki kontrybucji |
| `reviews_visible` | Czy lista opinii na profilu jest publiczna |
| `account_created_date` | Zawsze `null` (Google nie podaje) |

**Wskazówka:** różnica `review_count − review_count_visible` to opinie autora ukryte przez
Google. Wysoki odsetek ukrytych opinii u autorów danej wizytówki to najmocniejszy znany sygnał
fałszywych opinii.

---

### /review-visibility

Sprawdza, czy konkretna opinia jest publicznie widoczna. ~0,3–1 s.

`GET /api/review-visibility?url=<URL opinii>`

| Parametr | Opis |
|---|---|
| `url` | **Wymagany.** Link do opinii — patrz formaty niżej |

Obsługiwane formaty `url`:
- `https://www.google.com/maps/contrib/<ID autora>/place/<Place ID>/` — **zalecany**
- link „Udostępnij opinię” (`https://maps.app.goo.gl/...`)
- `https://www.google.com/maps/reviews/data=...`

```json
{
  "success": true,
  "data": {
    "review_url": "https://www.google.com/maps/contrib/.../place/.../",
    "is_visible": false,
    "visibility_reason": "no_rating",
    "checked_at": "2026-10-07 13:06:12"
  }
}
```

| `visibility_reason` | `is_visible` | Znaczenie |
|---|---|---|
| `visible` | true | Opinia widoczna publicznie |
| `no_rating` | false | Opinia nie jest widoczna (ukryta lub usunięta przez Google / autora) |
| `no_business_card` | false | Brak powiązanej wizytówki (usunięta) |
| `no_business_data` | false | Wizytówka bez danych do weryfikacji |
| `rate_limited_by_google` | false | Google zablokował zapytanie — **ponów później**, to nie jest werdykt |
| `failed_to_fetch` | false | Nie udało się pobrać strony — **ponów**, to nie jest werdykt |
| `unknown` | false | Nie udało się ustalić — **ponów**, to nie jest werdykt |

Traktuj `rate_limited_by_google`, `failed_to_fetch` i `unknown` jako „brak odpowiedzi”, a nie
„niewidoczna”.

---

### /review-details

Szczegóły pojedynczej opinii z linku (autor, ocena, tekst, miejsce) wraz z widocznością.

`GET /api/review-details?url=<URL opinii>`

Zwraca `data.review_details` z polami: `name`, `profile_name`, `review_text`, `date`,
`date_unix`, `rating`, `place_name`, `place_id`, `place_address`, `place_coordinates`,
`place_types`, `place_country`, `reviewer_id`, `reviewer_avatar`, `reviewer_profile_url`,
`reviewer_total_reviews`, `reviewer_total_photos`, `reviewer_level`, `reviewer_points`,
`review_id`, `review_language`, `review_length`, `photos_count`, `review_likes`, `is_visible`,
`visibility_reason`, `review_url`. Pola, których nie udało się odczytać, mają wartość `null`.
Do samej widoczności używaj szybszego `/review-visibility`.

---

### /info

Nazwa, wersja i lista endpointów. `GET /api/info`.

---

## 5. Endpointy POST

Ciało: `Content-Type: application/json`.

### /convert

```json
{ "cid": "9616850215896774958" }
```
→ `{ "cid": "...", "place_id": "ChIJBQyGzddtFkcRLnmqsFHqdYU", "conversion_type": "cid_to_place_id" }`

```json
{ "place_id": "ChIJBQyGzddtFkcRLnmqsFHqdYU" }
```
→ `{ "place_id": "...", "cid": "9616850215896774958", "conversion_type": "place_id_to_cid" }`

### /batch

Szczegóły kilku wizytówek naraz (pola jak w `details`).

```json
{ "place_ids": ["ChIJ...", "ChIJ..."], "max_concurrent": 3 }
```

- do **10** Place ID na żądanie, `max_concurrent` 1–5 (domyślnie 3);
- `data.results` to obiekt `{ "<place_id>": { ...pola details... } | null }` — `null` oznacza,
  że tej pozycji nie udało się pobrać (ponów ją pojedynczo przez `/details`);
- `data.performance`: `processed_count`, `success_count`, `execution_time`.

### /parse-review, /parse-place, /parse-user

Parsują surowy `APP_INITIALIZATION_STATE` ze strony Map, jeśli pobierasz HTML samodzielnie.
Ciało: `{ "app_state": <tablica JSON> }`. Odpowiedzi odpowiadają odpowiednio `review-details`,
`details` i `user-profile`.

---

## 6. Obiekt opinii

Element listy `reviews` w `place-reviews` i `review-job`.

```json
{
  "review_id": "Ci9DQUlRQUNvZENodHljRjlvT2xoek5EQkNlVFV0VXpSSVNXSjRiRUZWV1V...",
  "review_url": "https://www.google.com/maps/reviews/data=!4m8!14m7!...",
  "stars": 3,
  "review_text": "Zacznę od wystroju lokalu...",
  "review_language": "pl",
  "review_text_length": 1288,
  "likes_count": 1,
  "date_text": "miesiąc temu",
  "date_created": "2026-09-06T12:47:31.098Z",
  "date_created_ms": 1788698851098,
  "date_created_micros": 1788698851098000,
  "review_edited": false,
  "date_edited": null,
  "date_edited_micros": null,
  "review_source": "Google",
  "relevance_score": null,
  "author_name": "Jan K.",
  "author_id": "104085176304769949774",
  "author_profile_url": "https://www.google.com/maps/contrib/104085176304769949774/reviews",
  "author_avatar_url": "https://lh3.googleusercontent.com/a-/...",
  "author_review_count": 124,
  "author_photo_count": 406,
  "author_is_local_guide": true,
  "author_level": null,
  "photos_count": 1,
  "photos": [
    { "url": "https://lh3.googleusercontent.com/...", "id": "CIABIhByQXuC...", "proporcje": 1.333, "data_tekst": "miesiąc temu" }
  ],
  "owner_response_text": "Dziękujemy za opinię...",
  "has_owner_response": true,
  "owner_response_date": null,
  "owner_response_date_micros": null,
  "owner_response_date_text": "3 tygodnie temu",
  "owner_response_length": 270,
  "source": "boq"
}
```

| Pole | Typ | Opis |
|---|---|---|
| `review_id` | string | Stały identyfikator opinii — używaj do deduplikacji |
| `review_url` | string\|null | Bezpośredni link do opinii |
| `stars` | int 1–5 | Ocena |
| `review_text` | string\|null | Treść; `null` = sama ocena bez tekstu |
| `review_language` | string\|null | Kod języka treści (`pl`, `en`…) |
| `review_text_length` | int | Długość treści w znakach (0 bez tekstu) |
| `likes_count` | int | Liczba oznaczeń „Przydatne” |
| `date_text` | string | Data w postaci wyświetlanej („miesiąc temu”, „Edytowano rok temu”) |
| `date_created` | string (ISO) | Czas publikacji. **Dla opinii edytowanej — czas edycji** (patrz `review_edited`) |
| `date_created_ms` / `date_created_micros` | int | To samo w milisekundach / mikrosekundach od epoki |
| `review_edited` | bool | Czy opinia była edytowana |
| `date_edited` / `date_edited_micros` | string\|int\|null | Czas edycji (gdy `review_edited`), inaczej `null` |
| `review_source` | string | Zawsze `Google` |
| `author_name` | string | Nazwa autora |
| `author_id` | string | ID autora (do `/user-profile`) |
| `author_profile_url` | string | Profil autora w Mapach |
| `author_avatar_url` | string | Awatar autora |
| `author_review_count` | int\|null | Liczba opinii autora |
| `author_photo_count` | int\|null | Liczba zdjęć autora |
| `author_is_local_guide` | bool | Czy autor jest Local Guide |
| `photos_count` / `photos` | int / lista | Zdjęcia dołączone do opinii |
| `owner_response_text` | string\|null | Odpowiedź właściciela |
| `has_owner_response` | bool | Czy jest odpowiedź właściciela |
| `owner_response_date_text` | string\|null | Kiedy odpowiedział („3 tygodnie temu”) |
| `owner_response_length` | int\|null | Długość odpowiedzi w znakach |
| `source` | string | Źródło danych (informacyjne) |

**Pola zachowane dla zgodności, obecnie zawsze `null`:** `relevance_score`, `author_level`
(poziom Local Guide — pobierz z `/user-profile`), `owner_response_date`,
`owner_response_date_micros` (Google podaje tylko tekst daty — użyj `owner_response_date_text`).

---

## 7. Obiekt wizytówki (`details`)

Pola obiektu `data.details` w `/details` i elementów `data.results` w `/batch`.

**Identyfikacja i podstawy**

| Pole | Opis |
|---|---|
| `place_id_chij` | Place ID (ChIJ) |
| `cid` | CID (dziesiętny, jako string) |
| `entity_id` | Identyfikator encji Google (`/g/...`) |
| `nazwa_wizytowki` | Nazwa |
| `adres`, `adres_ulica`, `adres_kod`, `adres_miasto` | Adres pełny i w częściach |
| `lat`, `lng` | Współrzędne |
| `kraj`, `jezyk`, `strefa_czasowa` | Np. `PL`, `pl`, `Europe/Warsaw` |
| `czy_znaleziono` | `true` gdy wizytówka istnieje |

**Kategorie**

| Pole | Opis |
|---|---|
| `kategoria` | Kategoria główna (nazwa po polsku) |
| `kategoria_gcid` | Identyfikator kategorii głównej, np. `gcid:dental_clinic` — stabilny, niezależny od języka |
| `kategorie` | Wszystkie kategorie (nazwy) |
| `podtypy` | Typy techniczne (`restaurant`, `bar`…) |

**Oceny**

| Pole | Opis |
|---|---|
| `srednia_ocen` | Średnia ocena |
| `liczba_opinii` | Liczba opinii |
| `liczba_5_gwiazdek` … `liczba_1_gwiazdka` | Rozkład ocen |

**Kontakt i status**

| Pole | Opis |
|---|---|
| `strona_www`, `domena` | Strona WWW i jej domena |
| `telefon`, `telefon_raw` | Telefon sformatowany i same cyfry |
| `godziny` | Lista `{ "dzien": "środa", "godziny": ["12:00–21:00"] }` |
| `status_dzialalnosci` | `otwarte` \| `zamkniete_tymczasowo` \| `zamkniete_na_stale` \| `zamkniete` (rodzaj nieokreślony — wartość heurystyczna, zweryfikuj z `godziny`) |
| `rezerwacja_online`, `rezerwacja_dostawca`, `rezerwacja_url` | Rezerwacja przez Google (np. dostawca „Booksy”) |

**Właściciel i zarządzanie**

| Pole | Opis |
|---|---|
| `przejeta` | `true` = wizytówka ma właściciela (zweryfikowana) |
| `link_przejecia` | Link „Zostań właścicielem” — tylko gdy nieprzejęta |
| `zarzadca_id` | ID konta zarządzającego |
| `gbp_id` | ID konta Google Business Profile. Te same `gbp_id` / `zarzadca_id` u kilku wizytówek = jedno konto (sieć, agencja) |
| `opis`, `ma_opis` | Opis od właściciela |
| `liczba_postow`, `ostatni_post_data`, `ostatni_post_tekst_daty`, `ostatni_post_dni_temu`, `ostatni_post_tresc` | Posty właściciela (Google Posts) |

**Zdjęcia (podgląd)**

| Pole | Opis |
|---|---|
| `liczba_zdjec_podglad`, `zdjecia_miniatury` | Podgląd (~4 zdjęcia) — nie jest to łączna liczba zdjęć |
| `zdjecia_wlasciciela`, `zdjecia_klientow`, `ostatnie_zdjecie_wlasciciela`, `ostatnie_zdjecie_wlasciciela_dni_temu` | Liczone **z podglądu** — dolne ograniczenia. Dokładne wartości: `/place-photos` |

**Pozostałe**

| Pole | Opis |
|---|---|
| `atrybuty` | Lista `{ "grupa": "Dostępne opcje", "pozycje": ["Ogródek", ...] }` |
| `inni_wyszukiwali` | Konkurencja „Inni wyszukiwali też”: `{ nazwa, ocena, liczba_opinii, kategoria, kategorie, lat, lng, odleglosc_m, fid }` |
| `odznaki` | Odznaki wizytówki (lista, często pusta) |
| `popularne_godziny`, `szczyt_dzien`, `szczyt_godzina`, `szczyt_obciazenie` | Popularne godziny (obciążenie %) — `null` u miejsc bez tych danych |
| `reviews_pagination_key`, `reviews_pagination_key1`, `reviews_pagination_key2` | Wartości techniczne, do ignorowania |

**Cennik usług (`uslugi=1`)** — obiekt `data.uslugi`: `liczba_pozycji`, `liczba_kategorii`,
`pozycji_z_cena`, `pozycji_z_opisem`, `kategorie` (lista nazw), `pozycje` (lista
`{ kategoria, nazwa, opis, cena, czas, link }`, np. `"cena": "49,00 zł"`, `"czas": "1 godz. 15 min"`).
Występuje u ok. 25% wizytówek.

---

## 8. Błędy i limity

### Kody HTTP

| Kod | Kiedy | Co zrobić |
|---|---|---|
| 200 | Sukces (`success: true`) | — |
| 400 | Brak/zły parametr, nieistniejący Place ID, nic nie znaleziono | Popraw zapytanie; nie ponawiaj bez zmian |
| 401 | Nieprawidłowy klucz API (tylko gdy klucz jest wymagany — obecnie nie jest) | — |
| 404 | `review-job`: nieznane zadanie lub wynik wygasł (6 h) | Zgłoś zadanie ponownie |
| 429 | Przekroczony limit albo pełna kolejka zadań async | Ponów po czasie z `X-RateLimit-Reset` / po chwili |
| 500 | Błąd serwera | Ponów z odstępem |
| 503 | Usługa opinii chwilowo niedostępna | Ponów po 10–60 s |

### Limity

| Endpoint | Limit na godzinę |
|---|---|
| `/batch` | 1000 |
| `/all` | 2000 |
| pozostałe | 5000 |

Licznik jest per klucz API (gdy podany i rozpoznany), w przeciwnym razie per adres IP.
Nagłówki odpowiedzi: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`
(znacznik czasu Unix).

### Zalecenia dla integracji

- Timeout klienta: ≥ 30 s dla `place-reviews` synchronicznie (100 opinii), ≥ 60 s dla `batch`.
  Dla większych pobrań — `async=1`.
- Ponawiaj przy 429/500/503 z rosnącym odstępem (np. 5 s, 15 s, 60 s); przy 400 nie ponawiaj.
- Opinie deduplikuj po `review_id`.
- Nie wysyłaj kilku dużych pobrań tej samej wizytówki równolegle — dostaniesz te same dane.

---

## 9. Znane ograniczenia

- **Pytania i odpowiedzi (Q&A)** — Google wyłączył tę funkcję (XI–XII 2025); API ich nie zwraca.
- **Tematy opinii** („Klienci wspominają…”) — Google ich nie udostępnia; brak w API.
- `owner_response_date`, `relevance_score`, `author_level` w opiniach — zawsze `null` (rozdział 6).
- Dla opinii edytowanej `date_created` jest czasem edycji.
- `status_dzialalnosci = zamkniete` (bez rodzaju) bywa nietrafny — zweryfikuj godziny.
- Liczniki zdjęć w `details` są z podglądu; dokładne — `/place-photos`.
- Wyniki zależą od Google i mogą się zmienić bez zapowiedzi. Przy nagłej zmianie formatu dane
  przejściowo mogą być niepełne — zgłoś to, podając zapytanie i czas.

---

## 10. Historia zmian

### 2.0.0 — 2026-10-07
- **`/review-visibility`** — naprawa po zmianie strony Map (07.10, 00:00–13:05 UTC wszystkie
  sprawdzenia zwracały `unknown`; wyniki z tego okna należy powtórzyć).
- **`/search`, `POST /convert`** — naprawione wyszukiwanie po nazwie i konwersja CID → Place ID.
  `/search` zwraca dodatkowo `name`, `address`, `fid`, `cid`, `categories`, `rating`, `alternatives`.
- Dokumentacja przepisana; dodana specyfikacja OpenAPI.

### 2026-09-30
- **`/place-reviews`** — nowe źródło danych (duże wizytówki znów zwracają komplet opinii).
- Nowe wartości `sort`: `najnizsze`/`lowest`, `najwyzsze`/`highest`, `trafnosc`/`relevance`.
- Automatyczne `sort=najnizsze` przy `max_stars` ≤ 3 bez `sort` (pole `sort_auto`).
- Tryb `async=1` stosuje filtr `min_stars`/`max_stars`; zadania przeżywają restart.
- Pole `performance.zrodlo`. W opiniach nowe pola `date_text`, `owner_response_date_text`,
  `date_created_ms`; `relevance_score`, `author_level`, `owner_response_date` = `null`.

### 2026-08
- `/place-photos` (zdjęcia właściciela/klientów, urządzenie, statystyki), `/user-profile`
  (liczniki widocznych/ukrytych opinii), tryb `async=1` z `/review-job`, rozszerzone pola `details`
  (`kategoria_gcid`, `gbp_id`, posty, rezerwacje, popularne godziny, `uslugi=1`).

### 2026-01-02
- Endpoint `/place-reviews`.
