{"openapi":"3.1.0","info":{"title":"PretCarburant.ro API","version":"1.0.0","summary":"Preturi carburanti Romania: orase, judete, retele, statii.","description":"API JSON public pentru preturile carburantilor din Romania (benzina, motorina, GPL), agregate pe orase, judete si retele, plus date la nivel de statie.\n\n## Acces public vs. acces cu cheie API\nToate endpointurile raspund si FARA cheie, dar cu date sau limite reduse:\n- fara cheie: `/api/v1/preturi` intoarce top-20 orase, `/api/v1/statii` un esantion de 150 de inregistrari, iar endpointurile marcate cu cota publica accepta 1 cerere pe saptamana per IP si endpoint;\n- cu cheie (antet `X-Api-Key`): setul complet (~595 orase, toate inregistrarile de statii) si o cota lunara pe plan: Starter 10.000, Pro 100.000, Business 1.000.000 cereri/luna. Detalii si abonare: https://pretcarburant.ro/api-preturi-carburanti\n\n## Conventia preturilor lipsa (`preturi_lipsa`)\nUn pret pe care nu il avem vine ca `null` (niciun carburant nu costa 0 lei). Fiecare raspuns de preturi isi declara conventia in campul `preturi_lipsa`. Integrarile vechi pot cere sentinela `0` cu `?zeros=1`. Cine face medii peste raspuns trebuie sa excluda valorile lipsa, altfel obtine medii false.\n\n## Cota si antetele de raspuns\nCererile cu cheie valida primesc `X-RateLimit-Limit`, `X-RateLimit-Remaining` si `X-RateLimit-Reset` (timestamp Unix al resetarii lunare — metering pe luna calendaristica UTC). La depasirea cotei, raspunsul 429 le include si el, plus `Retry-After` in secunde.\n\nDate sub licenta CC-BY 4.0 cu atribuire \"PretCarburant.ro\".","contact":{"name":"PretCarburant.ro","url":"https://pretcarburant.ro/api-preturi-carburanti"},"license":{"name":"CC BY 4.0","url":"https://creativecommons.org/licenses/by/4.0/"}},"servers":[{"url":"https://pretcarburant.ro"}],"security":[{},{"ApiKeyAuth":[]}],"paths":{"/api/v1/preturi":{"get":{"operationId":"getPreturi","summary":"Preturi pe orase + medii pe retele","description":"Preturile minime curente pe orase, plus mediile pe retele. FARA cheie: top-20 orase (camp suplimentar `nota`). CU cheie `X-Api-Key`: toate orasele (~595). Endpoint fara cota publica saptamanala (e sursa widgetului embed); raspuns cacheabil 5 min.","security":[{},{"ApiKeyAuth":[]}],"parameters":[{"name":"judet","in":"query","required":false,"schema":{"type":"string"},"description":"Filtreaza orasele pe un judet: nume complet (cu/fara diacritice) sau cod auto (ex. CJ)."},{"name":"zeros","in":"query","required":false,"schema":{"type":"string","enum":["1","true","yes","da"]},"description":"Compatibilitate: readuce sentinela veche `0` pentru preturile lipsa (implicit ele vin `null`). Raspunsul isi declara conventia activa in campul `preturi_lipsa`. Orice alta valoare decat 1/true/yes/da lasa conventia `null`."}],"responses":{"200":{"description":"Lista de orase si retele cu preturi curente.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["status","data","total","rezultate","retele","preturi_lipsa"],"properties":{"status":{"type":"string","const":"ok"},"data":{"type":"string","format":"date","description":"Data serverului (ISO 8601)."},"total":{"type":"integer","description":"Numarul de orase din `rezultate`."},"rezultate":{"type":"array","items":{"$ref":"#/components/schemas/OrasPreturi"}},"retele":{"type":"array","items":{"$ref":"#/components/schemas/Retea"}},"preturi_lipsa":{"type":"string","enum":["null","zero"],"description":"Conventia activa pentru preturile lipsa in acest raspuns: \"null\" (implicit) sau \"zero\" (doar cu `?zeros=1`). Un client care face medii peste raspuns TREBUIE sa excluda valorile lipsa — un 0 tratat ca pret trage media in jos."},"nota":{"type":"string","description":"Doar fara cheie: explica limitarea la top-20 orase."}}}}}},"401":{"description":"Cheia API trimisa e invalida sau revocata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"429":{"description":"Limita de cereri atinsa. Doua cazuri, distinse dupa corp: (1) cu cheie API valida — cota lunara a planului e depasita (corp cu `limit` si `upgrade_url`; antetele X-RateLimit-* si Retry-After indica momentul resetarii lunare); (2) fara cheie — cota publica de 1 cerere/saptamana/IP/endpoint e consumata (corp cu `next_allowed` si `retry_after_seconds`).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/EroareCotaPlan"},{"$ref":"#/components/schemas/EroareCotaPublica"}]}}}}}}},"/api/v1/preturi/minime":{"get":{"operationId":"getPreturiMinime","summary":"Minim / medie / maxim national pe tip de carburant","description":"Agregate nationale publice. Fara autentificare si fara cota publica saptamanala; raspuns cacheabil 5 min.","security":[{}],"parameters":[{"name":"zeros","in":"query","required":false,"schema":{"type":"string","enum":["1","true","yes","da"]},"description":"Compatibilitate: readuce sentinela veche `0` pentru preturile lipsa (implicit ele vin `null`). Raspunsul isi declara conventia activa in campul `preturi_lipsa`. Orice alta valoare decat 1/true/yes/da lasa conventia `null`."}],"responses":{"200":{"description":"Bucket-urile nationale pe cele 5 tipuri de carburant.","content":{"application/json":{"schema":{"type":"object","required":["status","data","preturi","preturi_lipsa"],"properties":{"status":{"type":"string","const":"ok"},"data":{"type":"string","format":"date"},"preturi":{"type":"object","description":"Cheie = tip carburant.","properties":{"benzina_standard":{"$ref":"#/components/schemas/BucketNational"},"benzina_premium":{"$ref":"#/components/schemas/BucketNational"},"motorina_standard":{"$ref":"#/components/schemas/BucketNational"},"motorina_premium":{"$ref":"#/components/schemas/BucketNational"},"gpl":{"$ref":"#/components/schemas/BucketNational"}}},"preturi_lipsa":{"type":"string","enum":["null","zero"],"description":"Conventia activa pentru preturile lipsa in acest raspuns: \"null\" (implicit) sau \"zero\" (doar cu `?zeros=1`). Un client care face medii peste raspuns TREBUIE sa excluda valorile lipsa — un 0 tratat ca pret trage media in jos."}}}}}}}}},"/api/v1/judete/{judet}":{"get":{"operationId":"getJudet","summary":"Preturi medii agregate la nivel de judet","description":"Media peste toate orasele judetului. Accepta nume complet (cu/fara diacritice) sau cod auto. Sub cota publica saptamanala fara cheie.","security":[{},{"ApiKeyAuth":[]}],"parameters":[{"name":"judet","in":"path","required":true,"schema":{"type":"string"},"description":"Nume judet sau cod auto (ex. `cluj` sau `CJ`)."},{"name":"zeros","in":"query","required":false,"schema":{"type":"string","enum":["1","true","yes","da"]},"description":"Compatibilitate: readuce sentinela veche `0` pentru preturile lipsa (implicit ele vin `null`). Raspunsul isi declara conventia activa in campul `preturi_lipsa`. Orice alta valoare decat 1/true/yes/da lasa conventia `null`."}],"responses":{"200":{"description":"Mediile pe judet (null unde nu exista date).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["status","data","judet","nr_orase","preturi_lipsa"],"properties":{"status":{"type":"string","const":"ok"},"data":{"type":"string","format":"date"},"judet":{"type":"string","description":"Codul auto normalizat."},"nr_orase":{"type":"integer"},"benzina_standard":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"benzina_premium":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"motorina_standard":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"motorina_premium":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"gpl":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"preturi_lipsa":{"type":"string","enum":["null","zero"],"description":"Conventia activa pentru preturile lipsa in acest raspuns: \"null\" (implicit) sau \"zero\" (doar cu `?zeros=1`). Un client care face medii peste raspuns TREBUIE sa excluda valorile lipsa — un 0 tratat ca pret trage media in jos."}}}}}},"401":{"description":"Cheia API trimisa e invalida sau revocata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"404":{"description":"Judet necunoscut.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"429":{"description":"Limita de cereri atinsa. Doua cazuri, distinse dupa corp: (1) cu cheie API valida — cota lunara a planului e depasita (corp cu `limit` si `upgrade_url`; antetele X-RateLimit-* si Retry-After indica momentul resetarii lunare); (2) fara cheie — cota publica de 1 cerere/saptamana/IP/endpoint e consumata (corp cu `next_allowed` si `retry_after_seconds`).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/EroareCotaPlan"},{"$ref":"#/components/schemas/EroareCotaPublica"}]}}}}}}},"/api/v1/retele":{"get":{"operationId":"getRetele","summary":"Preturi medii nationale pe retele","security":[{},{"ApiKeyAuth":[]}],"parameters":[{"name":"zeros","in":"query","required":false,"schema":{"type":"string","enum":["1","true","yes","da"]},"description":"Compatibilitate: readuce sentinela veche `0` pentru preturile lipsa (implicit ele vin `null`). Raspunsul isi declara conventia activa in campul `preturi_lipsa`. Orice alta valoare decat 1/true/yes/da lasa conventia `null`."}],"responses":{"200":{"description":"Media nationala curenta a fiecarei retele.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["status","data","retele","preturi_lipsa"],"properties":{"status":{"type":"string","const":"ok"},"data":{"type":"string","format":"date"},"retele":{"type":"array","items":{"$ref":"#/components/schemas/Retea"}},"preturi_lipsa":{"type":"string","enum":["null","zero"],"description":"Conventia activa pentru preturile lipsa in acest raspuns: \"null\" (implicit) sau \"zero\" (doar cu `?zeros=1`). Un client care face medii peste raspuns TREBUIE sa excluda valorile lipsa — un 0 tratat ca pret trage media in jos."}}}}}},"401":{"description":"Cheia API trimisa e invalida sau revocata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"429":{"description":"Limita de cereri atinsa. Doua cazuri, distinse dupa corp: (1) cu cheie API valida — cota lunara a planului e depasita (corp cu `limit` si `upgrade_url`; antetele X-RateLimit-* si Retry-After indica momentul resetarii lunare); (2) fara cheie — cota publica de 1 cerere/saptamana/IP/endpoint e consumata (corp cu `next_allowed` si `retry_after_seconds`).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/EroareCotaPlan"},{"$ref":"#/components/schemas/EroareCotaPublica"}]}}}}}}},"/api/v1/statii":{"get":{"operationId":"getStatii","summary":"Inregistrari la nivel de statie (statie x carburant)","description":"Lista inregistrarilor statie+carburant, cu filtre pe brand, tip si zona geografica. FARA cheie: esantion de maximum 150 de inregistrari (campuri suplimentare `total_disponibil` si `nota`). CU cheie: setul complet (~6.000 de inregistrari). Sub cota publica saptamanala fara cheie.","security":[{},{"ApiKeyAuth":[]}],"parameters":[{"name":"brand","in":"query","required":false,"schema":{"type":"string"},"description":"Filtru pe brand, case-insensitive (ex. `omv`)."},{"name":"tip","in":"query","required":false,"schema":{"$ref":"#/components/schemas/TipCarburant"},"description":"Filtru pe tipul de carburant."},{"name":"lat","in":"query","required":false,"schema":{"type":"number"},"description":"Filtru geo (impreuna cu `lon`): doar statiile din raza data, sortate dupa distanta (adauga `distanta_km`)."},{"name":"lon","in":"query","required":false,"schema":{"type":"number"},"description":"Longitudine pentru filtrul geo. `lng` e acceptat ca alias."},{"name":"raza","in":"query","required":false,"schema":{"type":"number","minimum":0.1,"maximum":50,"default":10},"description":"Raza filtrului geo, in km (limitata la 0.1-50)."}],"responses":{"200":{"description":"Inregistrarile care trec de filtre.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["status","data","total","statii"],"properties":{"status":{"type":"string","const":"ok"},"data":{"type":"string","format":"date"},"total":{"type":"integer","description":"Numarul de inregistrari RETURNATE (dupa eventuala trunchiere publica)."},"statii":{"type":"array","items":{"$ref":"#/components/schemas/Statie"}},"total_disponibil":{"type":"integer","description":"Doar fara cheie, cand raspunsul e trunchiat: cate inregistrari exista in total."},"nota":{"type":"string","description":"Doar fara cheie: explica esantionul."}}}}}},"401":{"description":"Cheia API trimisa e invalida sau revocata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"429":{"description":"Limita de cereri atinsa. Doua cazuri, distinse dupa corp: (1) cu cheie API valida — cota lunara a planului e depasita (corp cu `limit` si `upgrade_url`; antetele X-RateLimit-* si Retry-After indica momentul resetarii lunare); (2) fara cheie — cota publica de 1 cerere/saptamana/IP/endpoint e consumata (corp cu `next_allowed` si `retry_after_seconds`).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/EroareCotaPlan"},{"$ref":"#/components/schemas/EroareCotaPublica"}]}}}}}}},"/api/v1/statie/{station_id}/istoric":{"get":{"operationId":"getStatieIstoric","summary":"Istoricul de pret al unei statii","description":"Serii zilnice de pret pentru o statie. `station_id` vine din campul `id` al raspunsului /api/v1/statii. Sub cota publica saptamanala fara cheie.","security":[{},{"ApiKeyAuth":[]}],"parameters":[{"name":"station_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"maximum":90},"description":"Cate zile de istoric (plafonat la 90)."}],"responses":{"200":{"description":"Seriile de pret ale statiei.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["status","station_id","labels","series","variatie"],"properties":{"status":{"type":"string","const":"ok"},"station_id":{"type":"string"},"labels":{"type":"array","items":{"type":"string"},"description":"Etichete de data pentru fiecare punct."},"series":{"type":"object","description":"Cheie = tip carburant, valoare = lista de preturi aliniata cu `labels`."},"variatie":{"type":"object","description":"Variatia pe fereastra ceruta, per tip de carburant."}}}}}},"401":{"description":"Cheia API trimisa e invalida sau revocata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"404":{"description":"Statie necunoscuta sau fara istoric.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"429":{"description":"Limita de cereri atinsa. Doua cazuri, distinse dupa corp: (1) cu cheie API valida — cota lunara a planului e depasita (corp cu `limit` si `upgrade_url`; antetele X-RateLimit-* si Retry-After indica momentul resetarii lunare); (2) fara cheie — cota publica de 1 cerere/saptamana/IP/endpoint e consumata (corp cu `next_allowed` si `retry_after_seconds`).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/EroareCotaPlan"},{"$ref":"#/components/schemas/EroareCotaPublica"}]}}}}}}},"/api/v1/cheie":{"get":{"operationId":"getCheieInfo","summary":"Statusul si consumul cheii API proprii","description":"Pentru dashboardul de client. NU consuma din cota lunara si nu e supus cotei publice.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Planul, limitele si consumul cheii.","content":{"application/json":{"schema":{"type":"object","required":["status","plan","limita_lunara","consum_luna"],"properties":{"status":{"type":"string","const":"ok"},"plan":{"type":"string"},"plan_nume":{"type":"string"},"limita_lunara":{"type":"integer"},"consum_luna":{"type":"integer"},"total_cereri":{"type":"integer"},"creata":{"type":"string"},"ultima_folosire":{"type":["string","null"]},"abonament":{"type":"boolean"},"zilnic":{"type":"array","items":{"type":"object","properties":{"zi":{"type":"string","format":"date"},"count":{"type":"integer"}}}}}}}}},"401":{"description":"Cheia API trimisa e invalida sau revocata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}}}}},"/api/v1/geocode":{"get":{"operationId":"geocodeOras","summary":"Autocomplete pentru nume de orase","security":[{},{"ApiKeyAuth":[]}],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2},"description":"Fragment din numele orasului (minim 2 caractere; sub 2 se intoarce lista goala)."}],"responses":{"200":{"description":"Cel mult 10 potriviri.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["results"],"properties":{"results":{"type":"array","items":{"type":"string"}}}}}}},"401":{"description":"Cheia API trimisa e invalida sau revocata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"429":{"description":"Limita de cereri atinsa. Doua cazuri, distinse dupa corp: (1) cu cheie API valida — cota lunara a planului e depasita (corp cu `limit` si `upgrade_url`; antetele X-RateLimit-* si Retry-After indica momentul resetarii lunare); (2) fara cheie — cota publica de 1 cerere/saptamana/IP/endpoint e consumata (corp cu `next_allowed` si `retry_after_seconds`).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/EroareCotaPlan"},{"$ref":"#/components/schemas/EroareCotaPublica"}]}}}}}}},"/api/v1/traseu/custom":{"post":{"operationId":"getStatiiPeTraseu","summary":"Statii de-a lungul unei rute A -> B","description":"Geocodeaza capetele, calculeaza ruta si intoarce statiile aflate in raza data de-a lungul ei. Sub cota publica saptamanala fara cheie. ATENTIE: erorile acestui endpoint folosesc forma `{ok, msg}`, nu `{status, message}`.","security":[{},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["start","end"],"properties":{"start":{"type":"string","examples":["Bucuresti"]},"end":{"type":"string","examples":["Brasov"]},"tip":{"allOf":[{"$ref":"#/components/schemas/TipCarburant"}],"default":"benzina_standard"},"raza":{"type":"number","default":5,"maximum":15,"description":"Raza in km fata de traseu (plafonata la 15)."}}}}}},"responses":{"200":{"description":"Ruta si statiile de pe ea (max 50 returnate).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["ok","km","statii"],"properties":{"ok":{"type":"boolean","const":true},"start":{"type":"object"},"end":{"type":"object"},"km":{"type":"number"},"duration_min":{"type":"number"},"tip":{"$ref":"#/components/schemas/TipCarburant"},"statii":{"type":"array","items":{"type":"object"}},"total_statii":{"type":"integer"},"waypoints":{"type":"array","items":{"type":"array","items":{"type":"number"}}}}}}}},"400":{"description":"Locatie negasita sau ruta imposibila.","content":{"application/json":{"schema":{"type":"object","required":["ok","msg"],"properties":{"ok":{"type":"boolean","const":false},"msg":{"type":"string"}}}}}},"401":{"description":"Cheia API trimisa e invalida sau revocata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"429":{"description":"Limita de cereri atinsa. Doua cazuri, distinse dupa corp: (1) cu cheie API valida — cota lunara a planului e depasita (corp cu `limit` si `upgrade_url`; antetele X-RateLimit-* si Retry-After indica momentul resetarii lunare); (2) fara cheie — cota publica de 1 cerere/saptamana/IP/endpoint e consumata (corp cu `next_allowed` si `retry_after_seconds`).","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/EroareCotaPlan"},{"$ref":"#/components/schemas/EroareCotaPublica"}]}}}}}}},"/api/v1/reviews/{slug}":{"get":{"operationId":"getReviews","summary":"Recenziile publice ale unei statii","security":[{}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"Slugul statiei (din paginile /statie/...)."}],"responses":{"200":{"description":"Rating mediu + cel mult 20 de recenzii aprobate.","content":{"application/json":{"schema":{"type":"object","required":["status","avg_rating","review_count","reviews"],"properties":{"status":{"type":"string","const":"ok"},"avg_rating":{"type":"number"},"review_count":{"type":"integer"},"reviews":{"type":"array","items":{"type":"object"}}}}}}}}}},"/api/v1/review":{"post":{"operationId":"postReview","summary":"Adauga un rating / o recenzie la o statie","description":"Rating simplu (doar stele) se accepta direct; textul liber cere token reCAPTCHA v3 si trece prin moderare.","security":[{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["statie_slug","rating"],"properties":{"statie_slug":{"type":"string"},"rating":{"type":"integer","minimum":1,"maximum":5},"nickname":{"type":"string","maxLength":50},"comment":{"type":"string","maxLength":500},"recaptcha_token":{"type":"string","description":"Obligatoriu cand exista text liber (comment sau nickname personalizat)."}}}}}},"responses":{"200":{"description":"Rating inregistrat.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","const":"ok"},"message":{"type":"string"},"avg_rating":{"type":"number"},"review_count":{"type":"integer"}}}}}},"400":{"description":"Slug lipsa sau rating invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"403":{"description":"Verificare reCAPTCHA esuata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"429":{"description":"Prea multe recenzii de la acelasi IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}}}}},"/api/v1/de-statii":{"get":{"operationId":"getStatiiGermania","summary":"Statii si preturi live din Germania (diaspora)","description":"Proxy pentru date Tankerkoenig/MTS-K (CC BY 4.0), in EUR. Canal separat de datele Romania. Limitat la 30 cereri/minut per IP.","security":[{}],"parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","minimum":47.0,"maximum":55.2},"description":"Latitudine, in interiorul Germaniei."},{"name":"lng","in":"query","required":true,"schema":{"type":"number","minimum":5.5,"maximum":15.5},"description":"Longitudine, in interiorul Germaniei."},{"name":"rad","in":"query","required":false,"schema":{"type":"number","minimum":1,"maximum":25,"default":15},"description":"Raza de cautare in km."}],"responses":{"200":{"description":"Statiile din raza, cu preturi in EUR.","content":{"application/json":{"schema":{"type":"object","required":["status","tara","moneda","statii"],"properties":{"status":{"type":"string","const":"ok"},"tara":{"type":"string","const":"DE"},"moneda":{"type":"string","const":"EUR"},"sursa":{"type":"string"},"atribuire":{"type":"string","format":"uri"},"lat":{"type":"number"},"lng":{"type":"number"},"rad_km":{"type":"number"},"nr_statii":{"type":"integer"},"statii":{"type":"array","items":{"type":"object"}},"generat_la":{"type":"string"}}}}}},"400":{"description":"Coordonate lipsa sau in afara Germaniei.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"429":{"description":"Peste 30 cereri/minut de la acelasi IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"502":{"description":"Sursa de date externa nu raspunde.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}},"503":{"description":"Serviciu temporar indisponibil.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}}}}},"/api/v1/push/vapid-key":{"get":{"operationId":"getVapidKey","summary":"Cheia publica VAPID pentru notificari web push","description":"Folosita de site-ul propriu (service worker). Nu e destinata integrarilor comerciale.","security":[{}],"responses":{"200":{"description":"Cheia publica VAPID.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","const":"ok"},"publicKey":{"type":"string"}}}}}},"500":{"description":"Cheia VAPID nu e configurata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}}}}},"/api/v1/push/subscribe":{"post":{"operationId":"pushSubscribe","summary":"Abonare la notificari web push","description":"Folosita de site-ul propriu (service worker). Nu e destinata integrarilor comerciale.","security":[{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["endpoint","p256dh","auth"],"properties":{"endpoint":{"type":"string"},"p256dh":{"type":"string"},"auth":{"type":"string"},"tip_carburant":{"type":"string","default":"benzina_standard"},"prag_pret":{"type":["number","null"]},"oras":{"type":"string","default":"Bucuresti"}}}}}},"responses":{"200":{"description":"Abonare inregistrata.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","const":"ok"},"message":{"type":"string"}}}}}},"400":{"description":"Campuri obligatorii lipsa.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Eroare"}}}}}}},"/api/v1/health":{"get":{"operationId":"getHealth","summary":"Health check: statusul si prospetimea datelor de pret","description":"Pentru integrari si monitorizare. Fara autentificare, fara cota. HTTP 200 cand datele sunt proaspete, HTTP 503 (status `degraded`) cand ultima actualizare e mai veche de 24h sau nu exista date de pret.","security":[{}],"responses":{"200":{"description":"Datele sunt proaspete si nevide.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}},"503":{"description":"Datele sunt invechite sau lipsesc.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/openapi.json":{"get":{"operationId":"getOpenApiSpec","summary":"Aceasta specificatie OpenAPI 3.1","security":[{}],"responses":{"200":{"description":"Documentul OpenAPI ca JSON.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/v1/openapi.json":{"get":{"operationId":"getOpenApiSpecAlias","summary":"Alias pentru /openapi.json","security":[{}],"responses":{"200":{"description":"Documentul OpenAPI ca JSON.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"Cheie API comerciala (format `pcro_live_...`). Se obtine de la https://pretcarburant.ro/api-preturi-carburanti. Fara cheie, endpointurile raman accesibile cu date/limite reduse."}},"headers":{"XRateLimitLimit":{"description":"Cota lunara a planului (numar de cereri). Prezent doar pe cererile cu cheie API valida.","schema":{"type":"integer"}},"XRateLimitRemaining":{"description":"Cereri ramase din cota lunara (0 cand e depasita). Prezent doar pe cererile cu cheie API valida.","schema":{"type":"integer"}},"XRateLimitReset":{"description":"Timestamp Unix (secunde UTC) al momentului in care se reseteaza cota: inceputul lunii calendaristice urmatoare (metering pe luna UTC). Prezent doar pe cererile cu cheie API valida.","schema":{"type":"integer"}},"RetryAfter":{"description":"Secunde pana cand cererea poate fi reincercata. La cota lunara depasita = secundele pana la resetarea lunii.","schema":{"type":"integer"}}},"schemas":{"Eroare":{"type":"object","required":["status","message"],"properties":{"status":{"type":"string","const":"error"},"message":{"type":"string"}}},"EroareCotaPlan":{"description":"Cota lunara a planului depasita (cerere CU cheie valida).","type":"object","required":["status","message","limit","upgrade_url"],"properties":{"status":{"type":"string","const":"error"},"message":{"type":"string"},"limit":{"type":"integer","description":"Cota lunara a planului."},"upgrade_url":{"type":"string","format":"uri"}}},"EroareCotaPublica":{"description":"Cota publica (fara cheie) consumata: 1 cerere pe saptamana per IP si endpoint.","type":"object","required":["status","message"],"properties":{"status":{"type":"string","const":"error"},"message":{"type":"string"},"next_allowed":{"type":"string","format":"date-time","description":"Momentul urmatoarei cereri permise."},"retry_after_seconds":{"type":"integer"}}},"OrasPreturi":{"type":"object","description":"Preturile minime curente dintr-un oras (RON/litru).","properties":{"oras":{"type":"string","examples":["Cluj-Napoca"]},"slug":{"type":"string","examples":["cluj-napoca"]},"judet":{"type":"string","description":"Codul auto al judetului (ex. CJ, B)."},"lat":{"type":"number"},"lng":{"type":"number"},"benzina":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"benzina_premium":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"motorina":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"motorina_premium":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"gpl":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."}}},"Retea":{"type":"object","description":"Pretul mediu national curent al unei retele de statii.","properties":{"nume":{"type":"string","examples":["Petrom"]},"slug":{"type":"string"},"culoare":{"type":"string","description":"Culoare hex a brandului (pentru UI)."},"logo":{"type":"string","description":"Cale relativa a logo-ului."},"benzina":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"benzina_premium":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"motorina":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"motorina_premium":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"gpl":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."}}},"BucketNational":{"type":"object","description":"Minim / medie / maxim national pentru un tip de carburant.","properties":{"min":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"mediu":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."},"max":{"type":["number","null"],"description":"Pret in RON/litru. `null` = pret indisponibil (conventia implicita `preturi_lipsa: \"null\"`); cu `?zeros=1` valorile lipsa vin ca `0` in loc de `null`."}}},"Statie":{"type":"object","description":"O inregistrare statie+carburant. O statie fizica apare o data pentru FIECARE tip de carburant vandut.","properties":{"id":{"type":"string","description":"Identificator stabil al statiei; se foloseste la /api/v1/statie/{station_id}/istoric."},"brand":{"type":"string","examples":["OMV"]},"oras":{"type":"string"},"judet":{"type":"string"},"adresa":{"type":"string"},"lat":{"type":"number"},"lng":{"type":"number"},"tip":{"$ref":"#/components/schemas/TipCarburant"},"pret":{"type":["number","null"],"description":"RON/litru; null = pret indisponibil."},"pret_expirat":{"type":"boolean","description":"Prezent (true) cand pretul a fost retras fiindca sursa lui nu a mai publicat de peste 24h."},"distanta_km":{"type":"number","description":"Doar cu filtrul geo (?lat&lon&raza): distanta fata de punctul cerut."}}},"TipCarburant":{"type":"string","enum":["benzina_standard","benzina_premium","motorina_standard","motorina_premium","gpl"]},"Health":{"type":"object","required":["status","statii_cu_pret"],"properties":{"status":{"type":"string","enum":["ok","degraded"]},"actualizat_la":{"type":["string","null"],"format":"date-time","description":"Momentul ultimei actualizari reusite a datelor de pret (ISO 8601). null cand nu poate fi determinat."},"vechime_secunde":{"type":["integer","null"],"description":"Vechimea datelor in secunde. Peste 86400 (24h) statusul devine degraded."},"statii_cu_pret":{"type":"integer","description":"Numarul de inregistrari cu pret valid — 0 inseamna ca nu servim date."}}}}}}