API public PretCarburant.ro — REST v1

Acces programatic la datele despre prețurile carburanților din România. Aceleași date pe care le folosim și noi pe site, actualizate la fiecare 2 ore. Toate endpoint-urile răspund JSON și funcționează și fără autentificare, cu date sau limite reduse; o cheie API (antetul X-Api-Key) deblochează setul complet și o cotă lunară pe plan. Licență Creative Commons BY 4.0 — folosește liber, cu atribuire.

Ai nevoie de mai mult decât cvota gratuită? Planuri comerciale de la 19 €/lună — 10.000–1.000.000 cereri/lună, trial 3 zile. Vezi planurile →

Quickstart: de la zero la primul apel

  1. Ia-ți o cheie API (opțional, dar recomandat). Abonează-te pe pagina de planuri — cheia, în formatul pcro_live_..., sosește pe email în câteva minute și vine cu 3 zile de probă gratuită. Poți sări peste acest pas: fără cheie, API-ul răspunde cu un set redus de date (detalii la Autentificare).

  2. Fă primul apel. Copiază comanda de mai jos în terminal (înlocuiește cheia; sau șterge linia cu -H ca să încerci varianta publică):

    curl -s -H "X-Api-Key: pcro_live_CHEIA_TA" \
      "https://pretcarburant.ro/api/v1/preturi" | jq '.rezultate[0]'
  3. Citește ce ai primit. Primul element din rezultate arată așa (răspuns real):

    {
      "benzina": 8.92,
      "benzina_premium": 9.4,
      "gpl": 4.23,
      "judet": "B",
      "lat": 44.42527,
      "lng": 26.01369,
      "motorina": 9.67,
      "motorina_premium": 10.49,
      "oras": "Bucuresti",
      "slug": "bucuresti"
    }

    Prețurile sunt în RON/litru, cele mai mici din orașul respectiv la momentul ultimei actualizări. Un preț pe care nu îl avem vine null, nu 0 (vezi Convenții de date). Cu cheie primești toate orașele (~595); fără cheie, top-20 plus un câmp nota care îți spune exact asta.

Autentificare: antetul X-Api-Key

Cheia API se trimite pe fiecare cerere, în antetul HTTP X-Api-Key. Nu există parametru de query pentru cheie și nu folosim Authorization: Bearer — doar antetul dedicat:

curl -s -H "X-Api-Key: pcro_live_CHEIA_TA" \
  "https://pretcarburant.ro/api/v1/statii?brand=omv"

Fără cheie nu primești eroare, ci un set redus — poți prototipa tot API-ul înainte să plătești un leu: /api/v1/preturi întoarce top-20 orașe în loc de toate (~595), /api/v1/statii un eșantion de 150 de înregistrări în loc de setul complet (~6.000), iar endpoint-urile marcate au o cotă publică de 1 cerere pe săptămână per IP și endpoint. Cu cheie validă dispare cota săptămânală, primești setul complet și antetele X-RateLimit-* pe fiecare răspuns.

O cheie invalidă sau revocată primește însă HTTP 401 — semnal clar, nu degradare tăcută la setul public (răspuns real):

{
  "message": "Invalid or revoked API key.",
  "status": "error"
}

Ține cheia pe server, nu în JavaScript-ul public al paginii tale: oricine o vede o poate folosi și îți consumă cota. Statusul și consumul propriei chei se verifică oricând, gratuit, la /api/v1/cheie.

Specificație OpenAPI 3.1

Întregul API v1 este descris formal într-o specificație OpenAPI 3.1, disponibilă la https://pretcarburant.ro/openapi.json (alias: /api/v1/openapi.json). Conține toate endpoint-urile, parametrii, formele de răspuns și codurile de eroare — o poți importa direct în Postman sau Insomnia, poți genera un client în limbajul tău cu openapi-generator sau o poți da unui asistent de cod AI ca să scrie integrarea pentru tine. Se servește fără rate limit, cu cache de o oră și CORS deschis.

curl -s https://pretcarburant.ro/openapi.json | jq '.info.version'

Endpoint-uri disponibile

Baza URL: https://pretcarburant.ro/api/v1/. Toate răspunsurile sunt JSON; endpoint-urile publice de agregate au Cache-Control: public, max-age=300 și Access-Control-Allow-Origin: *. Fiecare rând din tabel duce la secțiunea detaliată a endpoint-ului.

Endpoint-uri REST v1
EndpointDescriereRate limit fără cheie
GET /api/v1/preturi Prețuri agregate pe orașe (filtru opțional ?judet=CLUJ). Public: top 20 orașe; cu cheie API: toate (~595) Fără rate limit (cache 5 min)
GET /api/v1/preturi/minime Prețurile minime, medii și maxime la nivel național, per tip de carburant Fără rate limit (cache 5 min)
GET /api/v1/judete/<judet> Preț mediu agregat la nivel de județ (ex: /api/v1/judete/CLUJ) 1 cerere/săptămână per IP
GET /api/v1/retele Listă rețele monitorizate cu prețuri agregate per rețea 1 cerere/săptămână per IP
GET /api/v1/statii Listă stații cu coordonate, brand, prețuri și id stabil per stație (filtre: ?brand=, ?tip=, ?lat=&lon=&raza=). Public: eșantion 150 înregistrări; cu cheie API: setul complet 1 cerere/săptămână per IP
GET /api/v1/statie/<station_id>/istoric Istoricul prețurilor pentru o stație (station_id = câmpul id din /api/v1/statii; id necunoscut → 404) 1 cerere/săptămână per IP
GET /api/v1/geocode Autocomplete pentru numele orașelor (?q=cluj, minim 2 caractere) 1 cerere/săptămână per IP
POST /api/v1/traseu/custom Stații de-a lungul unei rute A→B (body JSON: start, end, tip) 1 cerere/săptămână per IP
GET /api/v1/cheie Statusul și consumul propriei chei API (necesită X-Api-Key; nu consumă din cotă)
GET /api/v1/reviews/<slug> Recenziile publice ale unei stații (rating mediu + max 20 recenzii aprobate) Fără rate limit
POST /api/v1/review Adaugă un rating/recenzie la o stație (text liber cere reCAPTCHA v3) Limite per IP (vezi secțiunea)
GET /api/v1/de-statii Stații și prețuri live din Germania (diaspora), în EUR 30 cereri/minut per IP
GET /api/v1/push/vapid-key
POST /api/v1/push/subscribe
Notificări web push — folosite de site-ul propriu, nu pentru integrări comerciale Fără rate limit
GET /api/v1/health Starea datelor: prospețime și număr de înregistrări cu preț Fără rate limit (fără cache)
GET /openapi.json Specificația OpenAPI 3.1 (alias: /api/v1/openapi.json) Fără rate limit (cache 1h)

Referință detaliată per endpoint

Toate exemplele de răspuns de mai jos sunt capturate din API-ul real, nu scrise de mână — valorile de preț sunt cele din ziua capturii, la tine vor fi cele curente. Listele lungi sunt trunchiate la primul element, cu un marcaj „… încă N" pentru rest.

GET /api/v1/preturi — prețuri pe orașe + medii pe rețele

Endpoint-ul principal: prețurile minime curente din fiecare oraș, plus media națională a fiecărei rețele — tot ce îți trebuie ca să afișezi „cât costă benzina în orașul X" sau să compari rețelele între ele. Fără cotă publică săptămânală (e sursa widgetului nostru embed); răspuns cacheabil 5 minute.

Parametri — GET /api/v1/preturi
ParametruTipObligatoriuImplicitConstrângeri / descriere
judetstringnuFiltrează orașele pe un județ: nume complet (cu/fără diacritice) sau cod auto (ex. CJ, cluj)
zerosstringnu1/true/yes/da readuce sentinela veche 0 pentru prețurile lipsă (vezi convenția)
curl -s https://pretcarburant.ro/api/v1/preturi | jq '.rezultate[0]'

Răspuns real (fără cheie, trunchiat):

{
  "data": "2026-09-02",
  "nota": "Acces public: top 20 orase. Setul complet (~595 orase) necesita o cheie API: https://pretcarburant.ro/api-preturi-carburanti",
  "preturi_lipsa": "null",
  "retele": [
    {
      "benzina": 8.92,
      "benzina_premium": 9.4,
      "culoare": "#E31937",
      "gpl": 3.72,
      "logo": "/static/img/brands/petrom.svg",
      "motorina": 9.73,
      "motorina_premium": 10.5,
      "nume": "Petrom",
      "slug": "petrom"
    },
    "… încă 7 rețele"
  ],
  "rezultate": [
    {
      "benzina": 8.92,
      "benzina_premium": 9.4,
      "gpl": 4.23,
      "judet": "B",
      "lat": 44.42527,
      "lng": 26.01369,
      "motorina": 9.67,
      "motorina_premium": 10.49,
      "oras": "Bucuresti",
      "slug": "bucuresti"
    },
    "… încă 19 orașe"
  ],
  "status": "ok",
  "total": 20
}
Câmpurile răspunsului — GET /api/v1/preturi
CâmpTipSemnificație
statusstringMereu "ok" pe 200
datastring (dată ISO 8601)Data serverului
totalintegerNumărul de orașe din rezultate
rezultate[]array de obiecteUn obiect per oraș: oras, slug, judet (cod auto), lat/lng (number) și cele 5 prețuri benzina, benzina_premium, motorina, motorina_premium, gpl (number sau null, RON/litru)
retele[]array de obiecteUn obiect per rețea: nume, slug, culoare (hex, pentru UI), logo (cale relativă) și aceleași 5 câmpuri de preț — media națională a rețelei
preturi_lipsastring"null" sau "zero" — convenția activă pentru prețurile lipsă
notastringDoar fără cheie: explică limitarea la top-20 orașe

Cu cheie: rezultate conține toate orașele (~595 — la captură, 586), câmpul nota dispare, iar răspunsul poartă antetele X-RateLimit-*.

GET /api/v1/preturi/minime — minim / medie / maxim național

Agregatele naționale pe cele 5 tipuri de carburant — pentru un banner „azi benzina e între X și Y lei" sau pentru monitorizarea trendului fără să procesezi orașele. Complet public: fără autentificare, fără cotă săptămânală, cache 5 minute.

Parametri — GET /api/v1/preturi/minime
ParametruTipObligatoriuImplicitConstrângeri / descriere
zerosstringnu1/true/yes/da — sentinela veche 0 pentru prețurile lipsă
curl -s https://pretcarburant.ro/api/v1/preturi/minime | jq '.preturi'

Răspuns real (complet):

{
  "data": "2026-09-02",
  "preturi": {
    "benzina_premium": {"max": 9.9, "mediu": 9.6, "min": 8.88},
    "benzina_standard": {"max": 9.42, "mediu": 8.98, "min": 8.69},
    "gpl": {"max": 5.0, "mediu": 4.34, "min": 3.61},
    "motorina_premium": {"max": 10.89, "mediu": 10.53, "min": 10.2},
    "motorina_standard": {"max": 10.49, "mediu": 9.74, "min": 9.27}
  },
  "preturi_lipsa": "null",
  "status": "ok"
}
Câmpurile răspunsului — GET /api/v1/preturi/minime
CâmpTipSemnificație
preturiobiectCheie = tipul de carburant (benzina_standard, benzina_premium, motorina_standard, motorina_premium, gpl)
preturi.*.min / mediu / maxnumber sau nullMinimul, media și maximul național, RON/litru
data, status, preturi_lipsaCa la /preturi

GET /api/v1/judete/<judet> — media pe județ

Media peste toate orașele unui județ — pentru integrări ERP/TMS care bugetează pe județe, nu pe orașe. Acceptă numele complet (cu sau fără diacritice) sau codul auto. Sub cota publică săptămânală fără cheie.

Parametri — GET /api/v1/judete/<judet>
ParametruTipObligatoriuImplicitConstrângeri / descriere
judet (în cale)stringdaNume județ sau cod auto: CLUJ, cluj, CJ, Timiș, timis
zerosstringnuSentinela veche 0 pentru prețurile lipsă
curl -s https://pretcarburant.ro/api/v1/judete/CLUJ | jq

Răspuns real (complet):

{
  "benzina_premium": 9.55,
  "benzina_standard": 8.92,
  "data": "2026-09-02",
  "gpl": 4.35,
  "judet": "CJ",
  "motorina_premium": 10.52,
  "motorina_standard": 9.65,
  "nr_orase": 15,
  "preturi_lipsa": "null",
  "status": "ok"
}
Câmpurile răspunsului — GET /api/v1/judete/<judet>
CâmpTipSemnificație
judetstringCodul auto normalizat (indiferent cum ai scris în URL)
nr_oraseintegerPeste câte orașe s-a făcut media
benzina_standardgplnumber sau nullMedia județului per tip de carburant, RON/litru; null când niciun oraș din județ nu are prețul respectiv

Județ necunoscut → HTTP 404 (răspuns real):

{
  "message": "Judet necunoscut: XX",
  "status": "error"
}

GET /api/v1/retele — mediile naționale pe rețele

Doar rețelele, fără orașe — pentru un comparator de branduri. Aceleași obiecte ca în câmpul retele din /preturi. Sub cota publică săptămânală fără cheie.

Parametri — GET /api/v1/retele
ParametruTipObligatoriuImplicitConstrângeri / descriere
zerosstringnuSentinela veche 0 pentru prețurile lipsă
curl -s https://pretcarburant.ro/api/v1/retele | jq '.retele[0]'

Răspuns real (trunchiat):

{
  "data": "2026-09-02",
  "preturi_lipsa": "null",
  "retele": [
    {
      "benzina": 8.92,
      "benzina_premium": 9.4,
      "culoare": "#E31937",
      "gpl": 3.72,
      "logo": "/static/img/brands/petrom.svg",
      "motorina": 9.73,
      "motorina_premium": 10.5,
      "nume": "Petrom",
      "slug": "petrom"
    },
    "… încă 7 rețele"
  ],
  "status": "ok"
}

GET /api/v1/statii — date la nivel de stație

Granularitatea maximă: fiecare înregistrare este o pereche stație × carburant — o stație fizică apare o dată pentru fiecare tip de carburant vândut. De aici iei coordonate pentru hartă, prețul exact de la o pompă anume și id-ul stabil cu care ceri istoricul. Sub cota publică săptămânală fără cheie.

Parametri — GET /api/v1/statii
ParametruTipObligatoriuImplicitConstrângeri / descriere
brandstringnuFiltru pe brand, case-insensitive (ex. omv, Petrom)
tipstringnuUnul din: benzina_standard, benzina_premium, motorina_standard, motorina_premium, gpl
latnumbernuÎmpreună cu lon activează filtrul geografic: doar stațiile din rază, sortate după distanță
lonnumbernuLongitudinea filtrului geo; lng e acceptat ca alias
razanumbernu10Raza filtrului geo, în km; limitată la intervalul 0.1–50
curl -s "https://pretcarburant.ro/api/v1/statii?brand=petrom&tip=motorina_standard" | jq '.statii[0]'

Răspuns real (fără cheie, trunchiat):

{
  "data": "2026-09-02",
  "nota": "Acces public: esantion de 150 inregistrari. Setul complet necesita o cheie API: https://pretcarburant.ro/api-preturi-carburanti",
  "statii": [
    {
      "adresa": "Str. Libertatii 19A, 515500",
      "brand": "Petrom",
      "id": "2f4185850d36",
      "judet": "Alba",
      "lat": 46.3618,
      "lng": 23.04885,
      "oras": "Campeni",
      "pret": 9.73,
      "tip": "motorina_standard"
    },
    "… încă 149 înregistrări"
  ],
  "status": "ok",
  "total": 150,
  "total_disponibil": 397
}
Câmpurile răspunsului — GET /api/v1/statii
CâmpTipSemnificație
totalintegerCâte înregistrări sunt în acest răspuns (după eventuala trunchiere publică)
statii[].idstringIdentificator stabil al stației — cheia pentru /statie/<id>/istoric
statii[].brand / oras / judet / adresastringIdentificarea stației; judet vine cu numele județului
statii[].lat / lngnumberCoordonatele stației
statii[].tipstringTipul de carburant al acestei înregistrări
statii[].pretnumber sau nullRON/litru; null = preț indisponibil
statii[].pret_expiratbooleanDoar când e cazul: true dacă prețul a fost retras fiindcă sursa lui nu a mai publicat de peste 24h
statii[].distanta_kmnumberDoar cu filtrul geo: distanța față de punctul cerut, km
total_disponibilintegerDoar fără cheie, când răspunsul e trunchiat: câte înregistrări există în total pentru filtrele date
notastringDoar fără cheie: explică eșantionul

Cu cheie: setul complet (~6.000 de înregistrări — la captură, 6.189 fără filtre), fără total_disponibil și nota.

GET /api/v1/statie/<station_id>/istoric — istoricul unei stații

Seriile zilnice de preț ale unei stații — pentru grafice și pentru „s-a scumpit sau s-a ieftinit aici?". station_id este câmpul id din /statii. Sub cota publică săptămânală fără cheie.

Parametri — GET /api/v1/statie/<station_id>/istoric
ParametruTipObligatoriuImplicitConstrângeri / descriere
station_id (în cale)stringdaCâmpul id al unei înregistrări din /api/v1/statii
daysintegernu30Câte zile de istoric; plafonat la 90
curl -s "https://pretcarburant.ro/api/v1/statie/2f4185850d36/istoric?days=7" | jq

Răspuns real (complet):

{
  "labels": ["27.08", "28.08", "29.08", "30.08", "31.08", "01.09", "02.09"],
  "series": {
    "benzina_premium": [9.99, 9.99, 9.99, 9.99, 9.99, 9.99, 10.03],
    "benzina_standard": [9.51, 9.51, 9.51, 9.51, 9.51, 9.51, 9.55],
    "motorina_premium": [10.91, 10.91, 10.91, 10.91, 10.91, 10.74, 10.74],
    "motorina_standard": [10.14, 10.14, 10.14, 10.14, 10.14, 9.97, 9.97]
  },
  "station_id": "2f4185850d36",
  "status": "ok",
  "variatie": {
    "benzina_premium": 0.04,
    "benzina_standard": 0.04,
    "motorina_premium": 0.0,
    "motorina_standard": 0.0
  }
}
Câmpurile răspunsului — istoric stație
CâmpTipSemnificație
labels[]array de stringEticheta de dată a fiecărui punct, în formatul de afișare zi.lună (ex. "27.08") — nu ISO 8601; ordinea e cronologică
seriesobiectCheie = tipul de carburant; valoare = lista de prețuri aliniată index-cu-index cu labels. Apar doar carburanții vânduți de stație
variatieobiectDiferența de preț pe fereastra cerută (ultimul punct minus primul), per tip de carburant, RON/litru

Id necunoscut sau stație fără istoric → HTTP 404 (răspuns real):

{
  "message": "Statie necunoscuta sau fara istoric. Foloseste campul `id` din /api/v1/statii.",
  "status": "error"
}

GET /api/v1/geocode — autocomplete orașe

Potrivește un fragment de text pe lista orașelor cunoscute de API — pentru un câmp de căutare cu sugestii. Sub cota publică săptămânală fără cheie.

Parametri — GET /api/v1/geocode
ParametruTipObligatoriuImplicitConstrângeri / descriere
qstringdaFragment din numele orașului; sub 2 caractere se întoarce lista goală
curl -s "https://pretcarburant.ro/api/v1/geocode?q=cluj" | jq

Răspuns real (complet) — cel mult 10 potriviri; răspunsul nu are câmpul status:

{
  "results": ["Cluj-Napoca"]
}

POST /api/v1/traseu/custom — stații de-a lungul unei rute

Geocodează capetele, calculează ruta rutieră și întoarce stațiile aflate în raza dată de-a lungul ei — pentru „unde alimentez pe drumul București–Brașov?". Singurul endpoint POST de date; corpul cererii e JSON. Sub cota publică săptămânală fără cheie.

Corpul cererii — POST /api/v1/traseu/custom
CâmpTipObligatoriuImplicitConstrângeri / descriere
startstringdaLocalitatea de plecare (ex. "Bucuresti")
endstringdaLocalitatea de sosire (ex. "Brasov")
tipstringnubenzina_standardTipul de carburant căutat (aceleași valori ca la /statii)
razanumbernu5Raza în km față de traseu; plafonată la 15
curl -s -X POST https://pretcarburant.ro/api/v1/traseu/custom \
  -H "Content-Type: application/json" \
  -d '{"start": "Bucuresti", "end": "Brasov", "tip": "motorina_standard", "raza": 5}'

Răspuns real (trunchiat). Atenție: acest endpoint folosește forma {ok, msg} pentru erori și ok: true pe succes, nu {status, message} ca restul API-ului:

{
  "duration_min": 165,
  "end": {"lat": 45.655, "lng": 25.611, "name": "Brasov"},
  "km": 182.0,
  "ok": true,
  "start": {"lat": 44.4268, "lng": 26.1025, "name": "Bucuresti"},
  "statii": [
    {
      "adresa": "Str. Fagarasului 2",
      "brand": "Rompetrol",
      "dist_ruta": 4.0,
      "lat": 45.662296,
      "lng": 25.574169,
      "oras": "Brasov",
      "pret": 9.67,
      "tip": "motorina_standard"
    },
    "… restul stațiilor (max 50 în răspuns)"
  ],
  "tip": "motorina_standard",
  "total_statii": 153,
  "waypoints": [
    [44.425874, 26.102435],
    [44.428773, 26.103985],
    "… restul punctelor de pe rută (max ~200)"
  ]
}
Câmpurile răspunsului — POST /api/v1/traseu/custom
CâmpTipSemnificație
okbooleantrue pe succes, false pe eroare
start / endobiectname, lat, lng — capetele geocodate
km, duration_minnumberLungimea rutei și durata estimată de condus, în minute
statii[]array de obiecteStațiile din rază, cu dist_ruta (km față de traseu), pret, brand, adresa, oras, lat/lng, tip; cel mult 50 returnate
total_statiiintegerCâte stații s-au găsit în total (poate depăși cele 50 returnate)
waypoints[]array de perechi [lat, lng]Poligonul rutei, subeșantionat la ~200 de puncte — suficient pentru desenat pe hartă

Localitate negăsită sau rută imposibilă → HTTP 400 (răspuns real):

{
  "msg": "Nu am gasit locatia",
  "ok": false
}

GET /api/v1/cheie — statusul propriei chei

Planul, limita și consumul cheii tale — pentru dashboard-ul tău intern sau pentru alerte de consum. Cere obligatoriu X-Api-Key, dar nu consumă din cota lunară și nu e supus cotei publice: verificarea propriului consum nu trebuie să coste.

curl -s -H "X-Api-Key: pcro_live_CHEIA_TA" https://pretcarburant.ro/api/v1/cheie | jq

Răspuns real (cheie de test pe planul Starter):

{
  "abonament": false,
  "consum_luna": 3,
  "creata": "2026-09-02T20:32:19+00:00",
  "limita_lunara": 10000,
  "plan": "starter",
  "plan_nume": "Starter",
  "status": "ok",
  "total_cereri": 3,
  "ultima_folosire": "2026-09-02T20:32:19+00:00",
  "zilnic": [{"count": 3, "zi": "2026-09-02"}]
}
Câmpurile răspunsului — GET /api/v1/cheie
CâmpTipSemnificație
plan / plan_numestringCodul și numele planului (Starter/Pro/Business; Dedicat pentru chei cu contract separat)
limita_lunaraintegerCota lunară efectivă a cheii
consum_lunaintegerCereri consumate în luna calendaristică UTC curentă
total_cereriintegerTotal istoric, peste toate lunile
creata / ultima_folosirestring (ISO 8601) / poate fi nullCând a fost emisă cheia și când a fost folosită ultima dată
abonamentbooleantrue dacă cheia e legată de un abonament Stripe activ
zilnic[]array de obiecteConsumul pe ultimele 30 de zile: {zi, count}

Fără cheie sau cu cheie greșită → HTTP 401 (răspuns real):

{
  "message": "Cheie invalida sau revocata.",
  "status": "error"
}

GET /api/v1/reviews/<slug> — recenziile unei stații

Ratingul mediu și cel mult 20 de recenzii aprobate ale unei stații. slug-ul este cel din URL-urile paginilor noastre de stație (/statie/<slug>). Fără autentificare și fără cotă.

curl -s https://pretcarburant.ro/api/v1/reviews/omv-alba-iulia-1 | jq

Răspuns real (complet):

{
  "avg_rating": 4.0,
  "review_count": 1,
  "reviews": [
    {
      "comment": "",
      "created_at": "2026-05-05 13:08:32",
      "id": 1,
      "nickname": "Anonim",
      "rating": 4
    }
  ],
  "status": "ok"
}

Un slug fără recenzii nu dă 404, ci avg_rating: 0, review_count: 0 și lista goală.

POST /api/v1/review — adaugă un rating

Trimite un rating (1–5 stele) sau o recenzie cu text la o stație. Ratingurile simple (doar stele) se acceptă direct; textul liber cere un token reCAPTCHA v3 și trece prin moderare înainte să apară public. Limite anti-abuz: o recenzie pe zi per stație per IP, maximum 3 recenzii pe zi per IP.

Corpul cererii — POST /api/v1/review
CâmpTipObligatoriuImplicitConstrângeri / descriere
statie_slugstringdaSlug-ul stației
ratingintegerdaÎntre 1 și 5
nicknamestringnuAnonimMaximum 50 de caractere
commentstringnuMaximum 500 de caractere
recaptcha_tokenstringcondiționalObligatoriu când există text liber (comentariu sau nickname personalizat)
curl -s -X POST https://pretcarburant.ro/api/v1/review \
  -H "Content-Type: application/json" \
  -d '{"statie_slug": "omv-alba-iulia-1", "rating": 5}'

Răspuns real la un rating simplu:

{
  "avg_rating": 5.0,
  "message": "Multumim pentru rating!",
  "review_count": 1,
  "status": "ok"
}

Rating lipsă sau în afara intervalului → HTTP 400 (răspuns real): {"message": "Rating invalid.", "status": "error"}. reCAPTCHA eșuat → 403; limitele per IP depășite → 429.

GET /api/v1/de-statii — stații din Germania (diaspora)

Prețuri live de la pompele din Germania, în EUR — pentru diaspora și pentru planificarea drumurilor prin Germania. Datele vin din Tankerkönig/MTS-K (CC BY 4.0) și sunt un canal complet separat de datele România: alte câmpuri, altă monedă. Limită proprie: 30 de cereri pe minut per IP (fără cotă săptămânală, fără cheie).

Parametri — GET /api/v1/de-statii
ParametruTipObligatoriuImplicitConstrângeri / descriere
latnumberdaLatitudine, în interiorul Germaniei (47.0–55.2)
lngnumberdaLongitudine, în interiorul Germaniei (5.5–15.5)
radnumbernu15Raza de căutare în km; limitată la 1–25
curl -s "https://pretcarburant.ro/api/v1/de-statii?lat=52.5200&lng=13.4050&rad=3" | jq '.statii[0]'

Răspuns real (Berlin, trunchiat):

{
  "atribuire": "https://www.tankerkoenig.de",
  "generat_la": "2026-09-02T23:31+03:00",
  "lat": 52.52,
  "lng": 13.405,
  "moneda": "EUR",
  "nr_statii": 10,
  "rad_km": 3.0,
  "statii": [
    {
      "adresa": "Holzmarktstraße 12/14",
      "benzina": 2.259,
      "brand": "ARAL",
      "cod_postal": 10179,
      "deschis": true,
      "dist_km": 1.3,
      "e10": 2.199,
      "lat": 52.514153,
      "lng": 13.421487,
      "motorina": 2.279,
      "nume": "Aral Tankstelle",
      "oras": "Berlin"
    },
    "… încă 9 stații"
  ],
  "status": "ok",
  "sursa": "Tankerkönig / MTS-K (CC BY 4.0)",
  "tara": "DE"
}
Câmpurile răspunsului — GET /api/v1/de-statii
CâmpTipSemnificație
tara / monedastringMereu "DE" / "EUR"
sursa / atribuirestringSursa datelor și linkul de atribuire cerut de licența ei
statii[].benzina / e10 / motorinanumber sau nullPrețuri în EUR/litru: Super E5, Super E10, motorină
statii[].deschisbooleanDacă stația e deschisă acum
statii[].dist_kmnumberDistanța față de punctul cerut, km
generat_lastring (ISO 8601)Momentul generării răspunsului (cache 10 minute pe server)

Coordonate lipsă sau în afara Germaniei → HTTP 400 (răspuns real): {"message": "Coordonatele trebuie să fie în Germania.", "status": "error"}. Sursa externă căzută → 502; serviciul neconfigurat → 503; peste 30 cereri/minut → 429.

Notificări web push — /api/v1/push/*

Două endpoint-uri folosite de service worker-ul site-ului nostru pentru alertele de preț din browser: GET /api/v1/push/vapid-key (cheia publică VAPID) și POST /api/v1/push/subscribe (înregistrează un abonament push: endpoint, p256dh, auth obligatorii, plus tip_carburant, prag_pret, oras opționale). Sunt documentate aici pentru completitudine — nu sunt destinate integrărilor comerciale și pot fi schimbate odată cu site-ul. Răspuns real de la cheia publică:

{
  "publicKey": "BE7ueOZzVNlQLqeFLGAb8Oq6rLc2sIhf8_j9gcl5FTgYwo9eB2YX8u23O0HaIO9NXJGDLM-ClmPHqeyz3mIAHrQ",
  "status": "ok"
}

GET /api/v1/health — starea datelor

Pentru monitorizare înainte și după punerea în producție: răspunde fără autentificare, fără rate limit și cu Cache-Control: no-store — un health check servit din cache ar ascunde exact incidentul pe care încerci să-l detectezi.

curl -s https://pretcarburant.ro/api/v1/health | jq

Răspuns pe date proaspete (HTTP 200):

{
  "status": "ok",
  "actualizat_la": "2026-09-02T06:30:00+00:00",
  "vechime_secunde": 4210,
  "statii_cu_pret": 5571
}

Codul HTTP spune tot: 200 = date proaspete și nevide; 503 (cu status: "degraded") = ultima actualizare e mai veche de 24 de ore sau nu servim niciun preț. Un monitor care verifică doar codul de stare este suficient. Corpul rămâne același și pe 503 — răspuns real, capturat pe un mediu cu date vechi:

{
  "actualizat_la": "2026-05-01T00:00:00+00:00",
  "statii_cu_pret": 5571,
  "status": "degraded",
  "vechime_secunde": 10787465
}

Catalog de erori

Corpurile de eroare de mai jos sunt capturate din API-ul real. Regula generală: forma e {"status": "error", "message": "..."} plus câmpuri suplimentare unde e cazul; singura excepție e /traseu/custom, care folosește {"ok": false, "msg": "..."}. Nu parsa textul din message — nu face parte din contract; decide după codul HTTP și după câmpurile structurate.

Coduri de eroare
CodCând apareCe să faci
400Cerere invalidă: rating în afara intervalului, coordonate în afara Germaniei, localitate negăsită pe rutăCorectează cererea; nu reîncerca aceeași cerere neschimbată
401Ai trimis X-Api-Key, dar cheia e invalidă sau revocatăVerifică cheia (spații, prefix pcro_live_); nu reîncerca automat — 401 nu se repară singur. Fără antetul X-Api-Key nu primești niciodată 401
404Județ necunoscut sau stație fără istoricVerifică inputul: codul de județ, respectiv câmpul id luat din /api/v1/statii
429 (cu cheie)Cota lunară a planului e depășităCitește Retry-After și antetele X-RateLimit-*; oprește cererile până la reset sau treci pe un plan mai mare
429 (fără cheie)Cota publică de 1 cerere/săptămână/IP/endpoint e consumatăFolosește next_allowed / retry_after_seconds din corp; pentru volum, ia o cheie API
502Doar /de-statii: sursa de date externă nu răspundeReîncearcă cu backoff — e o problemă tranzitorie a sursei
503/health pe date vechi/lipsă; /de-statii neconfiguratTratează ca „datele nu sunt de încredere acum": alertează, nu consuma răspunsul

401 — cheie invalidă (răspuns real):

{
  "message": "Invalid or revoked API key.",
  "status": "error"
}

429 cu cheie — cota lunară a planului depășită (răspuns real; vine împreună cu antetele X-RateLimit-* și Retry-After):

{
  "limit": 10000,
  "message": "Cvota lunara a planului (10000 cereri) a fost depasita.",
  "status": "error",
  "upgrade_url": "https://pretcarburant.ro/api-preturi-carburanti"
}

429 fără cheie — cota publică săptămânală consumată (răspuns real):

{
  "message": "Rate limit: 1 cerere pe saptamana. Urmatoarea cerere permisa peste 6z 23h.",
  "next_allowed": "2026-09-09T23:30:59.057642",
  "retry_after_seconds": 604793,
  "status": "error"
}

503 — health check pe date învechite (răspuns real): vezi exemplul din secțiunea health; corpul păstrează aceeași formă ca pe 200, cu status: "degraded".

Mostre de cod

Același apel — prețurile pe orașe, cu cheie API — în patru limbaje. Fiecare mostră tratează 401 și 429 și citește antetul X-RateLimit-Remaining, pentru că exact astea sunt lucrurile care se uită din happy path.

curl

curl -s -w "\nHTTP %{http_code}\n" \
  -H "X-Api-Key: pcro_live_CHEIA_TA" \
  "https://pretcarburant.ro/api/v1/preturi" | jq '.rezultate[0]'

Python (requests)

import requests

API_KEY = "pcro_live_CHEIA_TA"

r = requests.get(
    "https://pretcarburant.ro/api/v1/preturi",
    headers={"X-Api-Key": API_KEY},
    timeout=10,
)
if r.status_code == 401:
    raise SystemExit("Cheie API invalida sau revocata.")
if r.status_code == 429:
    raise SystemExit(f"Cota depasita; reincearca peste {r.headers.get('Retry-After')} secunde.")
r.raise_for_status()

print("Cereri ramase luna aceasta:", r.headers.get("X-RateLimit-Remaining"))
for oras in r.json()["rezultate"]:
    if oras["benzina"] is not None:  # pretul lipsa vine null, nu 0
        print(f'{oras["oras"]}: {oras["benzina"]} RON/l')

JavaScript (fetch, Node 18+)

const API_KEY = "pcro_live_CHEIA_TA";

const res = await fetch("https://pretcarburant.ro/api/v1/preturi", {
  headers: { "X-Api-Key": API_KEY },
});
if (res.status === 401) throw new Error("Cheie API invalida sau revocata.");
if (res.status === 429) {
  throw new Error(`Cota depasita; reincearca peste ${res.headers.get("Retry-After")} s`);
}
if (!res.ok) throw new Error(`HTTP ${res.status}`);

console.log("Cereri ramase luna aceasta:", res.headers.get("X-RateLimit-Remaining"));
const date = await res.json();
for (const oras of date.rezultate) {
  if (oras.benzina !== null) {  // pretul lipsa vine null, nu 0
    console.log(`${oras.oras}: ${oras.benzina} RON/l`);
  }
}

PHP (curl)

<?php
$apiKey = "pcro_live_CHEIA_TA";
$ramase = null;

$ch = curl_init("https://pretcarburant.ro/api/v1/preturi");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["X-Api-Key: $apiKey"],
    CURLOPT_TIMEOUT => 10,
    CURLOPT_HEADERFUNCTION => function ($ch, $header) use (&$ramase) {
        if (stripos($header, "X-RateLimit-Remaining:") === 0) {
            $ramase = trim(substr($header, 22));
        }
        return strlen($header);
    },
]);
$body = curl_exec($ch);
$cod  = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($cod === 401) { exit("Cheie API invalida sau revocata.\n"); }
if ($cod === 429) { exit("Cota depasita — vezi antetul Retry-After.\n"); }

echo "Cereri ramase luna aceasta: $ramase\n";
$date = json_decode($body, true);
foreach ($date["rezultate"] as $oras) {
    if ($oras["benzina"] !== null) {  // pretul lipsa vine null, nu 0
        echo $oras["oras"] . ": " . $oras["benzina"] . " RON/l\n";
    }
}

Convenții de date

Reguli valabile în tot API-ul v1 — citește-le o dată și scutești o zi de debugging:

Toate răspunsurile de succes ale endpoint-urilor de date au structura comună:

{
  "status": "ok",
  "data": "2026-09-02",
  "preturi_lipsa": "null",
  "rezultate": []
}

În caz de eroare:

{
  "status": "error",
  "message": "Rate limit: 1 cerere pe saptamana. Urmatoarea cerere permisa peste 6z 23h.",
  "retry_after_seconds": 604793
}

Prețuri lipsă: null, nu 0

Când nu avem prețul unui carburant într-un oraș sau la o rețea, câmpul vine null. Niciun carburant nu costă zero lei — intervalele reale sunt 5,50–12,00 RON/L la benzină, 5,50–13,00 la motorină și 2,50–6,00 la GPL — deci un 0 nu ar fi o măsurătoare, ci o valoare inventată care trage în jos orice medie calculată peste răspunsul nostru. Regula se aplică pe /preturi (atât în rezultate[], cât și în retele[]), pe /preturi/minime (câmpurile min, mediu, max), pe /judete/<judet> și pe /retele. Dacă faci medii peste răspuns, exclude valorile null — un zero tratat ca preț îți falsifică media.

Se convertesc numai câmpurile de preț. Un 0 la total, nr_orase, lat sau lng rămâne 0, pentru că acolo zero e un răspuns real, nu o valoare lipsă. Câmpul pret din /statii venea null și înainte.

{
  "oras": "Turda", "slug": "turda", "judet": "CJ",
  "lat": 46.5667, "lng": 23.7833,
  "benzina": 7.15, "benzina_premium": null,
  "motorina": 7.40, "motorina_premium": null, "gpl": null
}

Pentru integrările care nu pot fi actualizate odată cu noi, ?zeros=1 (acceptă și ?zeros=true) readuce exact comportamentul vechi, cu 0 în loc de null:

curl -s "https://pretcarburant.ro/api/v1/preturi?zeros=1" | jq '.rezultate[0]'

Fiecare răspuns își declară convenția în câmpul preturi_lipsa: "null" (implicit) sau "zero" (cu ?zeros=1). Citește-l în cod în loc să presupui — e singurul mod sigur de a ști ce ai primit. Conversia merge într-un singur sens: un null nu devine niciodată 0, nici cu ?zeros=1, pentru că /judete/<judet> răspundea null și înainte când nu avea din ce să facă media.

Limite și cote

Fără cheie: endpoint-urile de agregate /preturi și /preturi/minime nu au rate limit (cache 5 minute), la fel /reviews, /health și /openapi.json. Endpoint-urile /judete, /retele, /statii, /statie/<id>/istoric, /geocode și /traseu/custom au cota publică de 1 cerere pe săptămână per IP și endpoint. /de-statii are limita proprie de 30 cereri/minut per IP.

Cu cheie API: dispare cota săptămânală și primești o cotă lunară pe plan (luna calendaristică UTC):

Planuri și cote lunare
PlanPrețCotă lunară
Fără cheie (CC-BY 4.0)gratuitset redus + cota publică săptămânală
Starter19 €/lună10.000 cereri
Pro49 €/lună100.000 cereri
Business149 €/lună1.000.000 cereri (suport prioritar)

La depășirea cotei lunare primești 429 cu corpul din catalogul de erori, antetele X-RateLimit-* și Retry-After calculat până la resetarea reală a lunii. Apelurile către /api/v1/cheie nu consumă din cotă.

Pentru asistenții AI (ChatGPT, Claude, Perplexity, Gemini, Bing Copilot, etc.) am implementat un whitelist explicit pe User-Agent. Aceștia bypass-ează rate limit-ul săptămânal și au în loc o limită de 60 cereri/minut/IP, exact ca să poată cita datele noastre direct în răspunsuri. User-Agent-uri recunoscute: GPTBot, ChatGPT-User, OAI-SearchBot, ClaudeBot, anthropic-ai, PerplexityBot, Google-Extended, Googlebot, Applebot-Extended, Bytespider, Meta-ExternalAgent, CCBot, MistralAI, cohere-ai, YouBot, DiffBot, Bravebot și altele.

Antetele de cotă X-RateLimit-*

Orice cerere cu o cheie API validă (header X-Api-Key) primește trei antete de răspuns care spun exact unde te afli cu consumul lunar — pe orice endpoint, inclusiv pe cele publice:

Antetele vin și pe răspunsul 429 de cotă depășită — exact atunci când ai cea mai mare nevoie de ele — împreună cu Retry-After: numărul de secunde până la resetarea cotei, calculat real, nu o valoare fixă. Exemplu de citire:

curl -s -D - -o /dev/null -H "X-Api-Key: CHEIA_TA" \
  https://pretcarburant.ro/api/v1/statii

HTTP/2 200
X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 9977
X-RateLimit-Reset: 1790812800

1790812800 înseamnă 1 octombrie 2026, 00:00 UTC. Când X-RateLimit-Remaining se apropie de zero, e momentul să treci pe un plan mai mare — după resetare contorul pleacă din nou de la cota întreagă.

Licență și atribuire

Datele sunt publicate sub Creative Commons BY 4.0. Le poți folosi în produse comerciale și non-comerciale, cu atribuire vizibilă: "Sursa: PretCarburant.ro (https://pretcarburant.ro)".

Setul de date complet este publicat și pe Zenodo cu DOI: 10.5281/zenodo.19560194. Identitatea organizației pe Wikidata: Q139285387.

Acces comercial — planuri self-serve

Tier-ul gratuit CC-BY 4.0 rămâne neschimbat: 1 cerere/săptămână per IP pe endpoint-urile non-publice, cu atribuire vizibilă. Nu îl închidem și nu îl plafonăm — datele publice rămân publice.

Pentru aplicații, flote, redacții și orice integrare cu volum mare, există planuri comerciale cu activare imediată — cheia API sosește pe email în câteva minute după abonare:

  • Starter — 19 €/lună, 10.000 cereri/lună
  • Pro — 49 €/lună, 100.000 cereri/lună
  • Business — 149 €/lună, 1.000.000 cereri/lună, suport prioritar

Toate includ 3 zile de probă gratuită, acces la toate endpoint-urile fără cvota săptămânală și headerele X-RateLimit-* pentru monitorizarea consumului. Vezi planurile și abonează-te →

Nevoi speciale (SLA contractual, export bulk, snapshot-uri istorice, drepturi extinse fără atribuire)? Scrie-ne pentru o ofertă dedicată.

Versionare și compatibilitate

Ce garantăm pentru /api/v1 — o politică simplă pe care chiar o putem ține:

Istoric versiuni

Contact și raportare bug-uri

Email: contact@pretcarburant.ro. Pentru bug-uri tehnice sau cereri de feature, scrie cu subiectul prefixat „[API]". Răspundem în maxim 48h în zilele lucrătoare.

Disclosure responsabilă pentru probleme de securitate: vezi /.well-known/security.txt.