API 4pricing pentru dezvoltatori
Conectează-ți magazinul online, ERP-ul sau un tabel la 4pricing: citești prețurile concurenților, trimiți prețurile și vânzările tale și primești schimbările de preț printr-un webhook semnat. Nu trebuie să fii programator ca să începi — integrarea rapidă de mai jos îți dă un fișier gata făcut, pe care doar îl urci pe site.
Adresa de bază: https://4pricing.net/api/v1/pricing · Specificația OpenAPI (JSON)
Integrare rapidă în 3 pași
Este cel mai scurt drum ca să vezi datele din 4pricing pe propriul site. Copiezi un singur fișier, îți lipești cheia în el și îl urci pe hosting — fără framework, fără instalări.
- Un abonament 4pricing activ (API-ul e inclus în orice plan).
- Un hosting cu PHP — îl are aproape orice hosting obișnuit (cPanel, Plesk și altele).
- O cale de a urca un fișier: managerul de fișiere al hostingului sau un program FTP.
Creează-ți cheia API
Intră în panel.4pricing.net și deschide Monitorizare Prețuri → API & chei.
Apasă „Generează cheie”, dă-i un nume (de exemplu „magazinul meu”) și copiază cheia care începe cu prc_live_. Ea se afișează o singură dată — dacă o pierzi, creezi alta și o revoci pe cea veche.
Copiază fișierul gata făcut și lipește-ți cheia
Copiază fișierul PHP de mai jos într-un editor de text (Notepad, TextEdit sau VS Code) și salvează-l cu numele 4pricing-preturi.php.
php4pricing-preturi.php
<?php // ===================================================================== // LIPEȘTE AICI CHEIA TA API (panel.4pricing.net → API & chei), între ghilimele $apiKey = 'PUNE_AICI_CHEIA_TA'; // ===================================================================== header('Content-Type: text/html; charset=utf-8'); function h($text) { return htmlspecialchars((string) $text, ENT_QUOTES, 'UTF-8'); } function money($cents) { if ($cents === null) { return '—'; } return number_format($cents / 100, 2, ',', '.'); } if (strpos($apiKey, 'prc_') !== 0) { exit('<p>' . h('Lipsește cheia API: deschide fișierul și lipește-ți cheia pe rândul marcat.') . '</p>'); } $ch = curl_init('https://4pricing.net/api/v1/pricing/groups/'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_USERAGENT => 'my-shop-integration/1.0', CURLOPT_TIMEOUT => 30, CURLOPT_HTTPHEADER => ['X-API-Key: ' . $apiKey, 'Accept: application/json'], ]); $body = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($body === false) { exit('<p>' . h('Nu m-am putut conecta la 4pricing:' . ' ' . curl_error($ch)) . '</p>'); } if ($status !== 200) { $hints = [401 => 'cheia API e greșită sau a fost revocată', 402 => 'contul nu are un abonament 4pricing activ (sau nu are destule credite)', 429 => 'prea multe cereri într-un minut, încearcă din nou peste un minut']; $hint = isset($hints[$status]) ? ' — ' . $hints[$status] : ''; exit('<p>' . h('4pricing a răspuns cu eroarea' . ' ' . $status . $hint) . '</p><pre>' . h($body) . '</pre>'); } $groups = json_decode($body, true); $verdicts = [ 'cheapest' => 'Ești cel mai ieftin', 'too_cheap' => 'Mult sub piață — poți crește prețul', 'competitive' => 'Competitiv', 'too_expensive' => 'Mai scump decât majoritatea concurenților', 'no_competitors' => 'Încă nu există preț de la concurență', 'no_own_price' => 'Lipsește prețul tău', ]; echo '<!doctype html><html><head><meta charset="utf-8"><title>' . h('Produsele mele monitorizate') . '</title>'; echo '<style>body{font-family:Arial,sans-serif;margin:24px}table{border-collapse:collapse}'; echo 'th,td{border:1px solid #ddd;padding:6px 10px;text-align:left}th{background:#f5f5f5}</style>'; echo '</head><body><h1>' . h('Produsele mele monitorizate') . '</h1>'; if (!$groups) { echo '<p>' . h('Nu ai încă niciun produs comparat.') . '</p>'; } else { echo '<table><tr><th>' . h('Produs') . '</th><th>' . h('Cod (SKU)') . '</th>'; echo '<th>' . h('Prețul tău') . '</th><th>' . h('Cel mai ieftin concurent') . '</th>'; echo '<th>' . h('Diferența') . '</th><th>' . h('Verdict') . '</th></tr>'; foreach ($groups as $g) { $yours = $g['own_price_cents_effective']; $cheapest = $g['min_competitor_cents']; $diff = '—'; if ($yours !== null && $cheapest !== null) { $diff = ($yours > $cheapest ? '+' : '') . money($yours - $cheapest); } $verdict = isset($verdicts[$g['verdict']]) ? $verdicts[$g['verdict']] : $g['verdict']; echo '<tr><td>' . h($g['name']) . '</td><td>' . h($g['sku']) . '</td>'; echo '<td>' . money($yours) . '</td><td>' . money($cheapest) . '</td>'; echo '<td>' . $diff . '</td><td>' . h($verdict) . '</td></tr>'; } echo '</table>'; } echo '</body></html>';Înlocuiește PUNE_AICI_CHEIA_TA cu cheia ta, păstrând ghilimelele din jurul ei. E singurul rând pe care trebuie să-l modifici.Urcă fișierul pe site și deschide-l în browser
Urcă 4pricing-preturi.php pe site cu managerul de fișiere sau prin FTP, în același folder cu site-ul (de obicei public_html).
Deschide în browser https://site-ul-tau/4pricing-preturi.php. Vei vedea un tabel cu fiecare produs comparat: prețul tău, cel mai mic preț al concurenței, diferența și verdictul.
Fișierul: lista produselor tale, cu cel mai mic preț al concurenței
Fișierul cere de la 4pricing produsele tale comparate (GET /groups/) și le afișează într-un tabel. Citirea datelor e gratuită și nu consumă credite.
- „Prețul tău” este prețul pe care 4pricing îl știe pentru tine; „Cel mai ieftin concurent” este cel mai mic preț găsit la concurenții pe care îi monitorizezi.
- Prețurile apar în lei/euro, nu în bani/cenți (12999 devine 129,99); o liniuță (—) înseamnă că 4pricing nu are încă un preț, nu că prețul e zero.
- Dacă în loc de tabel vezi o eroare, mesajul îți spune ce ai de reparat: cheie greșită, abonament inactiv sau prea multe cereri într-un minut.
A doua rețetă: trimite prețul tău
Folosește-o când vrei ca 4pricing să afle prețul tău fără să-l citească de pe pagina magazinului — de exemplu direct din baza de date a magazinului. Trimiterea prețurilor tale e gratuită (fără credite) și nu intră în limita de URL-uri monitorizate a planului.
| Câmp | Ce înseamnă |
|---|---|
| sku | codul produsului la tine; 4pricing găsește produsul după el sau îl creează prima dată |
| name | numele produsului, așa cum vrei să-l vezi în 4pricing |
| url | opțional: adresa paginii produsului din magazinul tău (nu este citită niciodată) |
| price_cents | prețul înmulțit cu 100, fără zecimale: 49,99 → 4999 |
| currency | codul monedei, de exemplu RON sau EUR (RON dacă lipsește) |
php4pricing-trimite-pret.php
<?php
// =====================================================================
// LIPEȘTE AICI CHEIA TA API (panel.4pricing.net → API & chei), între ghilimele
$apiKey = 'PUNE_AICI_CHEIA_TA';
// =====================================================================
// PRODUSELE TALE: câte un bloc pentru fiecare produs. price_cents = prețul x 100 (49,99 devine 4999).
$products = [
[
'sku' => 'TRICOU-ALB-M',
'name' => 'Tricou alb, mărimea M',
'url' => 'https://magazinul-tau.ro/tricou-alb-m',
'price_cents' => 4999,
'currency' => 'RON',
],
];
header('Content-Type: text/html; charset=utf-8');
function h($text) { return htmlspecialchars((string) $text, ENT_QUOTES, 'UTF-8'); }
if (strpos($apiKey, 'prc_') !== 0) {
exit('<p>' . h('Lipsește cheia API: deschide fișierul și lipește-ți cheia pe rândul marcat.') . '</p>');
}
$ch = curl_init('https://4pricing.net/api/v1/pricing/own-products/bulk');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => 'my-shop-integration/1.0',
CURLOPT_TIMEOUT => 60,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . $apiKey, 'Content-Type: application/json', 'Accept: application/json'],
CURLOPT_POSTFIELDS => json_encode(['items' => $products], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($body === false) {
exit('<p>' . h('Nu m-am putut conecta la 4pricing:' . ' ' . curl_error($ch)) . '</p>');
}
if ($status !== 200) {
$hints = [401 => 'cheia API e greșită sau a fost revocată', 402 => 'contul nu are un abonament 4pricing activ (sau nu are destule credite)', 429 => 'prea multe cereri într-un minut, încearcă din nou peste un minut'];
$hint = isset($hints[$status]) ? ' — ' . $hints[$status] : '';
exit('<p>' . h('4pricing a răspuns cu eroarea' . ' ' . $status . $hint) . '</p><pre>' . h($body) . '</pre>');
}
$result = json_decode($body, true);
echo '<h1>' . h('Prețuri trimise la 4pricing') . '</h1>';
echo '<p>' . h('Produse noi create:') . ' ' . (int) $result['created'] . '</p>';
echo '<p>' . h('Produse actualizate:') . ' ' . (int) $result['updated'] . '</p>';
if (!empty($result['errors'])) {
echo '<p>' . h('Rânduri cu probleme:') . '</p><ul>';
foreach ($result['errors'] as $error) {
echo '<li>' . h($error) . '</li>';
}
echo '</ul>';
}
- Salvează-l ca 4pricing-trimite-pret.php, lipește-ți cheia, completează rândurile cu produsele tale, urcă-l și deschide-l o dată: pagina îți arată câte produse au fost create și câte actualizate.
- O cerere poate avea până la 2.000 de produse; un rând greșit e raportat, iar celelalte se salvează. Dacă trimiți din nou același sku, prețul se actualizează.
Aceleași apeluri cu curl
Dacă preferi terminalul, aceste două comenzi fac exact ce fac cele două fișiere PHP:
bash
curl https://4pricing.net/api/v1/pricing/groups/ \
-H "X-API-Key: PUNE_AICI_CHEIA_TA"bash
curl -X POST https://4pricing.net/api/v1/pricing/own-products/bulk \
-H "X-API-Key: PUNE_AICI_CHEIA_TA" \
-H "Content-Type: application/json" \
-d '{"items":[{"sku":"TRICOU-ALB-M","name":"Tricou alb, mărimea M","url":"https://magazinul-tau.ro/tricou-alb-m","price_cents":4999,"currency":"RON"}]}'- Cheia dă acces la tot contul tău 4pricing. Țin-o doar în fișiere care rulează pe server (PHP, backend-ul tău) — niciodată în JavaScript-ul din browser sau într-un repository public.
- Oricine știe adresa paginii urcate poate vedea tabelul cu prețuri (nu și cheia). Dă fișierului un nume greu de ghicit, protejează folderul cu parolă sau șterge-l când nu mai ai nevoie de el.
- Dacă o cheie ajunge unde nu trebuie, revoc-o din API & chei — se oprește imediat — și creează una nouă.
Autentificare
- Fiecare cerere are cheia ta în antetul X-API-Key. Merge și Authorization: Bearer prc_live_… (doar pentru cheile care încep cu prc_).
- Nu există drepturi parțiale (scope-uri): o cheie poate citi și modifica tot ce e în contul tău 4pricing. Creează câte o cheie pentru fiecare integrare (magazin, ERP, raport), ca să poți revoca una fără să le oprești pe celelalte.
- O cheie lipsă, greșită sau revocată primește 401. Revocarea are efect imediat. Prea multe chei invalide într-un minut primesc 429.
- Cheile se creează, se văd și se revocă doar din panou (Monitorizare Prețuri → API & chei); API-ul nu poate gestiona chei. Crearea unei chei cere un abonament 4pricing activ.
http
X-API-Key: prc_live_…
# or
Authorization: Bearer prc_live_…Adresa de bază și convenții
- Toate căile din această documentație sunt relative la adresa de bază — /groups/ înseamnă https://4pricing.net/api/v1/pricing/groups/.
- Cererile și răspunsurile sunt JSON (Content-Type: application/json).
- Sumele sunt numere întregi în subunități (bani/cenți): price_cents = 12999 înseamnă 129,99. Procentele sunt în puncte de bază (100 = 1%).
- O valoare necunoscută este null, niciodată 0 — afișeaz-o ca „—”.
- Colecțiile se apelează cu slash la final (/products/, /groups/, /insights/).
- Listele paginate întorc totalul în antetul X-Total-Count (de exemplu GET /products/?skip=0&limit=200).
- Trimite un antet User-Agent cu numele integrării tale (de exemplu my-shop-integration/1.0). Firewall-ul din fața API-ului răspunde cu 403 la unele valori implicite ale bibliotecilor, cum e Python-urllib.
Limita de cereri, abonamentul și limitele planului
Limita de cereri
- 60 de cereri pe minut pe CONT, într-o fereastră glisantă — toate cheile aceluiași cont împart aceleași 60.
- Fiecare răspuns are antetele X-RateLimit-Limit, X-RateLimit-Remaining și X-RateLimit-Reset (lungimea ferestrei, în secunde).
- Peste limită primești 429 cu antetul Retry-After (secunde). Aștepți atât, apoi reîncerci — exemplele de mai jos fac asta pentru tine.
http
HTTP/2 200
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
x-ratelimit-reset: 60
HTTP/2 429
retry-after: 60
{"detail":"Rate limit exceeded"}Abonamentul și limitele planului
- API-ul cere un abonament 4pricing activ; fără el, orice cerere primește 402 cu codul plan_required.
- Orice plan include API-ul, cu aceleași limite ca în panou. Dacă depășești o limită când adaugi sau reactivezi ceva, primești 402 cu codul plan_limit și un mesaj care spune ce limită.
| Limită | Ce înseamnă |
|---|---|
| URL-uri monitorizate | Fiecare adresă de pagină de produs pe care 4pricing o citește se numără separat: pagina ta și fiecare pagină de concurent. Prețurile trimise prin /own-products nu se numără. |
| Magazine concurente | Numărul de site-uri (domenii) diferite ale concurenților pe care îi monitorizezi. |
| Intervalul minim de verificare | Cât de des poate fi verificat același URL. Intervalele mai scurte sunt urcate automat la minimul planului; o reîmprospătare prea devreme primește 402 plan_limit. |
Ce apeluri consumă credite
Citirea unei pagini de magazin costă credite din portofelul contului. Apelurile de mai jos verifică soldul ÎNAINTE să citească ceva: dacă nu ajunge primești 402 insufficient_credits, iar dacă portofelul nu poate fi citit acum primești 503 credit_wallet_unavailable (reîncearcă mai târziu). În ambele cazuri nu se citește nimic și nu se taxează nimic.
Apeluri care consumă credite
| Metodă | Endpoint | Ce face | Cost |
|---|---|---|---|
| POST | /groups/{group_id}/members | Adaugă un URL în grup, după product_id sau dintr-o adresă nouă (o adresă nouă e citită imediat) | Credite |
| POST | /groups/{group_id}/refresh | Verifică acum toate URL-urile grupului | Credite |
| POST | /groups/{group_id}/suggestions | „Caută concurența”: URL-uri de concurenți propuse, cu scor și motiv | Credite |
| POST | /products/ | Începe monitorizarea unui URL (url, name, role own sau competitor) — e citit imediat | Credite |
| POST | /products/{product_id}/refresh | Verifică acum prețul acestui URL (respectând intervalul minim al planului) | Credite |
| POST | /search/ | Caută prețul live al unui produs la retaileri (credite pentru fiecare retailer interogat) | Credite |
Apeluri gratuite
- Toate citirile (GET): produse, grupuri, comparații, istoric, alerte, insight-uri, briefing-uri, credite, statistici.
- POST /own-products/ și POST /own-products/bulk — prețurile tale.
- POST /sales/bulk — vânzările tale.
- Alertele, setările, confirmările și tot ce nu citește o pagină de magazin.
- Monitorizarea programată a URL-urilor urmărite consumă și ea credite, la fiecare verificare; GET /credits/ îți arată soldul și prognoza.
- POST /products/import adaugă multe URL-uri deodată fără să le citească imediat: ele sunt verificate, și taxate, de monitorizarea programată.
Lista de endpoint-uri
Toate operațiile la care ajunge o cheie, grupate după rostul lor. Specificația OpenAPI le descrie pe fiecare câmp cu câmp, cu corpurile exacte de cerere și de răspuns.
Prețurile tale
Îi spui direct lui 4pricing prețul tău, fără să-ți citească pagina magazinului. Gratuit.
| Metodă | Endpoint | Ce face | Cost |
|---|---|---|---|
| POST | /own-products/ | Trimite prețul tău curent pentru un produs (găsit după sku, altfel după nume; creat dacă nu există) | Gratuit |
| POST | /own-products/bulk | Trimite prețurile tale pentru până la 2.000 de produse într-o cerere; rândurile greșite sunt raportate, restul se salvează | Gratuit |
Produse comparate (grupuri)
Un grup este un produs de-al tău împreună cu paginile concurenților care vând același lucru. Aici stau verdictul și prețul recomandat.
| Metodă | Endpoint | Ce face | Cost |
|---|---|---|---|
| GET | /groups/ | Toate produsele comparate, fiecare cu prețul tău, cel mai mic preț al concurenței, verdictul și prețul recomandat | Gratuit |
| POST | /groups/ | Creează un produs comparat (nume, sku, prețul tău, cost, strategie de preț) | Gratuit |
| GET | /groups/{group_id} | Un produs comparat | Gratuit |
| PUT | /groups/{group_id} | Modifică prețul, costul, strategia, intervalul de verificare sau legătura cu magazinul (store_kind) | Gratuit |
| DELETE | /groups/{group_id} | Șterge grupul; URL-urile lui rămân monitorizate | Gratuit |
| GET | /groups/{group_id}/comparison | Poziția ta față de fiecare concurent, cu prețul recomandat și felul în care a fost calculat | Gratuit |
| GET | /groups/{group_id}/history | Istoricul de preț al fiecărui URL din grup | Gratuit |
| GET | /groups/series | Poziția ta pe piață, zi cu zi, pe toate produsele | Gratuit |
| POST | /groups/{group_id}/members | Adaugă un URL în grup, după product_id sau dintr-o adresă nouă (o adresă nouă e citită imediat) | Credite |
| DELETE | /groups/{group_id}/members/{product_id} | Scoate un URL din grup (rămâne monitorizat) | Gratuit |
| POST | /groups/{group_id}/refresh | Verifică acum toate URL-urile grupului | Credite |
| POST | /groups/{group_id}/apply-recommended | Face din prețul recomandat prețul tău în 4pricing (trimiți prețul pe care l-ai văzut; 409 dacă s-a schimbat) | Gratuit |
| GET | /groups/suggestions/sources | Sursele disponibile pentru „Caută concurența” | Gratuit |
| POST | /groups/{group_id}/suggestions | „Caută concurența”: URL-uri de concurenți propuse, cu scor și motiv | Credite |
URL-uri monitorizate
Câte un rând pentru fiecare adresă de pagină pe care o citește 4pricing, a ta sau a unui concurent (role own sau competitor).
| Metodă | Endpoint | Ce face | Cost |
|---|---|---|---|
| GET | /products/ | URL-urile monitorizate, paginat (skip, limit până la 500; totalul în X-Total-Count) | Gratuit |
| POST | /products/ | Începe monitorizarea unui URL (url, name, role own sau competitor) — e citit imediat | Credite |
| POST | /products/import | Adaugă multe URL-uri deodată; sunt citite mai târziu, de monitorizarea programată | Gratuit |
| GET | /products/export | Export CSV cu URL-urile active | Gratuit |
| GET | /products/{product_id} | Un URL monitorizat, cu ultimul preț | Gratuit |
| PATCH | /products/{product_id} | Schimbă numele, categoria sau intervalul de verificare | Gratuit |
| DELETE | /products/{product_id} | Oprește monitorizarea unui URL (rămâne păstrat ca inactiv) | Gratuit |
| POST | /products/{product_id}/refresh | Verifică acum prețul acestui URL (respectând intervalul minim al planului) | Credite |
| GET | /products/{product_id}/history | Istoricul de preț al unui URL | Gratuit |
| GET | /products/sparklines | Un istoric scurt de preț pentru mai multe URL-uri deodată | Gratuit |
| GET | /products/price-changes | URL-urile al căror preț s-a schimbat la ultima verificare | Gratuit |
Căutare live
Afli chiar acum prețul unui produs la retaileri.
| Metodă | Endpoint | Ce face | Cost |
|---|---|---|---|
| POST | /search/ | Caută prețul live al unui produs la retaileri (credite pentru fiecare retailer interogat) | Credite |
Alerte
Regulile de preț și inbox-ul alertelor declanșate.
| Metodă | Endpoint | Ce face | Cost |
|---|---|---|---|
| GET | /alerts/ | Alertele tale de preț | Gratuit |
| POST | /alerts/ | Creează o alertă (preț sub/peste, scădere în %, revenire în stoc, depășit de un concurent…) | Gratuit |
| DELETE | /alerts/{alert_id} | Șterge o alertă | Gratuit |
| POST | /alerts/{alert_id}/rearm | Rearmează o alertă declanșată | Gratuit |
| GET | /alerts/events | Inbox-ul alertelor declanșate | Gratuit |
| GET | /alerts/events/unread-count | Câte alerte necitite ai | Gratuit |
| POST | /alerts/events/read-all | Marchează alertele ca citite (opțional doar pe cele de dinaintea unui moment) | Gratuit |
| POST | /alerts/events/{event_id}/read | Marchează o alertă ca citită | Gratuit |
Credite
Soldul, prognoza și fiecare mișcare de credite.
| Metodă | Endpoint | Ce face | Cost |
|---|---|---|---|
| GET | /credits/ | Soldul de credite și prognoza de consum | Gratuit |
| GET | /credits/ledger | Fiecare mișcare de credite | Gratuit |
| GET | /credits/by-product | Creditele consumate pe fiecare URL (total și ultimele 30 de zile) | Gratuit |
Vânzările tale
Vânzările zilnice pe produs, din care se calculează valoarea fiecărei oportunități. Gratuit.
| Metodă | Endpoint | Ce face | Cost |
|---|---|---|---|
| POST | /sales/bulk | Vânzările zilnice pe produs (sku sau group_id, day, units_sold, revenue_cents; opțional stock_units, cost_cents), până la 2.000 de rânduri; dacă trimiți din nou același produs și aceeași zi, se suprascrie | Gratuit |
Insight-uri, oportunități și briefing
Ce s-a schimbat, de ce contează și ce ai de făcut — plus briefingul zilnic.
| Metodă | Endpoint | Ce face | Cost |
|---|---|---|---|
| GET | /insights/ | Insight-uri cu dovezi și propuneri de preț (X-Total-Count) | Gratuit |
| GET | /insights/{insight_id} | Un insight în detaliu | Gratuit |
| POST | /insights/{insight_id}/dismiss | Ignoră un insight | Gratuit |
| GET | /opportunities/ | Sumarul oportunităților: valoarea din vânzările tale, separată de estimarea de piață | Gratuit |
| GET | /briefings/ | Istoricul briefingurilor zilnice | Gratuit |
| GET | /briefings/latest | Ultimul briefing zilnic | Gratuit |
| POST | /briefings/generate | Reconstruiește briefingul de azi din insight-urile deschise (nu trimite nimic) | Gratuit |
Acțiuni de preț
Schimbările de preț propuse, trimise, aplicate sau anulate. Prin API le poți citi și le poți confirma pe cele primite prin webhook.
| Metodă | Endpoint | Ce face | Cost |
|---|---|---|---|
| GET | /actions/ | Acțiunile de preț: propuse, trimise, aplicate, anulate | Gratuit |
| POST | /actions/{action_id}/confirm | Confirmă că sistemul tău a aplicat un preț primit prin price.apply sau price.revert | Gratuit |
Setări
Setările contului, inclusiv adresa și secretul webhook-ului.
| Metodă | Endpoint | Ce face | Cost |
|---|---|---|---|
| GET | /settings/ | Setările contului (retaileri, strategia de preț, livrarea alertelor, webhook) | Gratuit |
| PUT | /settings/ | Modifică setările, inclusiv alert_webhook_url, alert_webhook_secret și alert_webhook_enabled | Gratuit |
Statistici
Situația contului și starea verificărilor de preț.
| Metodă | Endpoint | Ce face | Cost |
|---|---|---|---|
| GET | /stats/ | Statistici de cont: URL-uri, alerte, schimbări în 24 de ore, grupuri | Gratuit |
| GET | /stats/scrapers | Starea verificărilor de preț, pe retaileri | Gratuit |
53 endpoints · https://4pricing.net/api/v1/pricing/openapi.json
Ce nu poate face API-ul
Unele lucruri rămân intenționat în panou, unde decide o persoană autentificată. Prin API, aceste căi răspund cu 404:
| Cale | De ce |
|---|---|
| /keys/… | Crearea și revocarea cheilor — o cheie nu poate crea alte chei. |
| /autopilot/… | Politica de autopilot, care decide ce se poate schimba fără o persoană; o cheie scăpată nu trebuie să o poată lărgi. |
| /watchlists/… | Listele de urmărire, care consumă credite la fiecare rulare. |
| /actions/{action_id}/approve · reject · revert | Aprobarea, respingerea sau anularea unei schimbări de preț este o decizie pe care o ia o persoană în panou. API-ul poate doar să citească acțiunile și să le confirme. |
| /insights/{insight_id}/act · /insights/run | Acțiunea pe un insight (scrie prețuri) și recalcularea insight-urilor. |
| /admin/… · /internal/… | Administrarea internă a platformei. |
Webhook-uri
4pricing poate trimite evenimente către o adresă a ta (un webhook): face un POST JSON de fiecare dată când se întâmplă ceva, ca sistemul tău să nu trebuiască să tot întrebe.
Există o singură adresă de webhook pe cont; toate tipurile de evenimente ajung la ea. Le deosebești după câmpul event — alerta e singurul eveniment fără el.
Cum îl pornești
- Setezi alert_webhook_url (o adresă publică http/https, pe portul 80 sau 443), alert_webhook_secret și alert_webhook_enabled: true — din panou sau cu PUT /settings/.
- O adresă care duce într-o rețea privată sau internă e refuzată cu 422. Redirecționările nu sunt urmate.
- Pentru briefing.ready pornește și briefing_webhook_enabled. price.apply și price.revert se trimit doar pentru grupurile legate de magazinul tău cu store_kind = webhook și doar dacă ai setat un secret.
bash
curl -X PUT https://4pricing.net/api/v1/pricing/settings/ \
-H "X-API-Key: PUNE_AICI_CHEIA_TA" \
-H "Content-Type: application/json" \
-d '{"alert_webhook_url":"https://your-shop.example/4pricing-webhook.php","alert_webhook_secret":"SECRETUL_TAU_DE_WEBHOOK","alert_webhook_enabled":true,"briefing_webhook_enabled":true}'Evenimente
| Eveniment | Ce înseamnă |
|---|---|
| alert | S-a declanșat o alertă de preț. Corp: event_id (deduplici după el), alert_id, alert_type, message, message_code, message_params, price_cents, currency, triggered_at, product, group, panel_url. Fără câmpul event. |
| price.apply | Un preț aprobat, de aplicat în magazinul tău: action_id, group (id, name, sku, ean, store_product_id), old_price_cents, new_price_cents, currency, reason_code, mode și confirm.path. |
| price.revert | Revenirea la prețul anterior. Același corp ca price.apply; îl aplici și îl confirmi la fel. |
| briefing.ready | Briefingul zilnic e gata: day, headline, summary, sections, total_value_cents (doar din vânzările tale), currency, panel_url. |
Antetele fiecărei cereri de webhook
| Antet | Ce înseamnă |
|---|---|
| X-4Pricing-Timestamp | Momentul trimiterii, în secunde Unix. Respinge cererile prea vechi (de exemplu peste 5 minute). |
| X-4Pricing-Signature | sha256= urmat de HMAC-SHA256 în hex al textului „<timestamp>.<corpul brut>”, calculat cu secretul tău de webhook. Prezent când ai setat un secret (mereu pe price.apply și price.revert). |
| Content-Type | application/json; charset=utf-8 |
| User-Agent | 4pricing-alerts/1.0 |
Verificarea semnăturii
Recalculezi semnătura din antetul de timestamp și din corpul BRUT primit, o compari în timp constant și verifici timestamp-ul. Abia apoi procesezi evenimentul. Răspunde cu orice cod 2xx când l-ai acceptat.
php
<?php
$secret = 'SECRETUL_TAU_DE_WEBHOOK';
// Folosește corpul BRUT, exact cum l-ai primit — nu un JSON recodificat
$body = file_get_contents('php://input');
$timestamp = isset($_SERVER['HTTP_X_4PRICING_TIMESTAMP']) ? $_SERVER['HTTP_X_4PRICING_TIMESTAMP'] : '';
$signature = isset($_SERVER['HTTP_X_4PRICING_SIGNATURE']) ? $_SERVER['HTTP_X_4PRICING_SIGNATURE'] : '';
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret);
// Semnătură greșită sau mai veche de 5 minute: respinge cererea
if (!hash_equals($expected, $signature) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
if (isset($event['event']) && ($event['event'] === 'price.apply' || $event['event'] === 'price.revert')) {
// Aplică new_price_cents produsului din magazinul tău (îl găsești după sku sau store_product_id)
// $event['group']['sku'], $event['new_price_cents'], $event['currency']
// Apoi confirmă: POST /actions/{action_id}/confirm cu cheia ta API
}
http_response_code(200);
echo 'ok';
javascript
// Node.js, no package needed
const http = require("http");
const crypto = require("crypto");
const SECRET = process.env.WEBHOOK_SECRET; // SECRETUL_TAU_DE_WEBHOOK
http.createServer((req, res) => {
const chunks = [];
req.on("data", (c) => chunks.push(c));
req.on("end", () => {
// Folosește corpul BRUT, exact cum l-ai primit — nu un JSON recodificat
const body = Buffer.concat(chunks);
const ts = String(req.headers["x-4pricing-timestamp"] || "");
const sig = String(req.headers["x-4pricing-signature"] || "");
const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(ts + ".").update(body).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
const same = sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!same || !fresh) {
// Semnătură greșită sau mai veche de 5 minute: respinge cererea
res.writeHead(401);
return res.end();
}
const event = JSON.parse(body.toString("utf8"));
if (event.event === "price.apply" || event.event === "price.revert") {
// Aplică new_price_cents produsului din magazinul tău (îl găsești după sku sau store_product_id)
// Apoi confirmă: POST /actions/{action_id}/confirm cu cheia ta API
}
res.writeHead(200);
res.end("ok");
});
}).listen(3000);
python
# Python 3, standard library only
import hashlib
import hmac
import time
SECRET = "SECRETUL_TAU_DE_WEBHOOK"
def is_valid(raw_body: bytes, timestamp: str, signature: str) -> bool:
# Folosește corpul BRUT, exact cum l-ai primit — nu un JSON recodificat
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
return False
expected = "sha256=" + hmac.new(
SECRET.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Livrare și reîncercări
- Alerte: la o eroare de conexiune sau DNS, la 429 sau la 5xx, cererea se reîncearcă cu pauze din ce în ce mai mari, de cel mult 5 ori. Orice alt cod 4xx e definitiv.
- Dacă cererea a plecat, dar răspunsul n-a venit în 10 secunde, nu se mai retrimite (ca să nu se dubleze) — poți primi totuși, rar, un duplicat, așa că deduplici după event_id.
- price.apply și price.revert se trimit o singură dată: dacă livrarea eșuează, acțiunea devine failed. Deduplici după action_id.
- briefing.ready se trimite o dată pe zi.
Confirmarea unui preț aplicat
- Un price.apply nu se consideră aplicat până nu spune sistemul tău asta. După ce schimbi prețul în magazin, apelezi POST /actions/{action_id}/confirm cu cheia ta API: acțiunea trece din „trimisă” în „aplicată”.
- O confirmare repetată nu strică nimic — o acțiune deja aplicată e întoarsă așa cum este.
bash
curl -X POST https://4pricing.net/api/v1/pricing/actions/ACTION_ID/confirm \
-H "X-API-Key: PUNE_AICI_CHEIA_TA"Exemple de corp
json · price.apply
{
"event": "price.apply",
"action_id": "8d0f2c4e-0b7a-4f7e-9d61-3c2b1a0e9f55",
"tenant_id": "…",
"group": {
"id": "2fa912df-440c-4ddf-896a-5d558d56085f",
"name": "Canon RF 50mm F1.8 STM",
"sku": "4515C005AA",
"ean": "4549292181623",
"store_product_id": "1042"
},
"old_price_cents": 124900,
"new_price_cents": 118699,
"currency": "RON",
"reason_code": "undercut",
"mode": "manual",
"confirm": { "method": "POST", "path": "/api/v1/pricing/actions/8d0f2c4e-0b7a-4f7e-9d61-3c2b1a0e9f55/confirm" }
}json · alert
{
"event_id": 18342,
"alert_id": "c1b5…",
"alert_type": "competitor_undercut",
"tenant_id": "…",
"message_code": "competitor_undercut",
"message": "…",
"message_params": { "…": "…" },
"price_cents": 118990,
"currency": "RON",
"triggered_at": "2026-10-13T07:05:12",
"product": { "id": "…", "name": "…", "url": "https://…", "retailer": "…", "current_price_cents": 118990, "in_stock": true },
"group": { "id": "…", "name": "Canon RF 50mm F1.8 STM", "sku": "4515C005AA" },
"panel_url": "https://panel.4pricing.net/…"
}Erori
Erorile vin cu statusul HTTP potrivit și un corp JSON {"detail": …}. detail este un text, un obiect cu un cod pe care îl poți trata în program sau — la 422 — lista câmpurilor invalide.
| Status | Cod | Ce înseamnă |
|---|---|---|
| 401 | — | Cheia API lipsește, e greșită sau a fost revocată. |
| 402 | plan_required | Contul nu are un abonament 4pricing activ. |
| 402 | plan_limit | S-ar depăși o limită a planului (URL-uri monitorizate, magazine concurente) sau o reîmprospătare a venit înainte de intervalul minim. Mesajul spune care. |
| 402 | insufficient_credits | Nu sunt destule credite pentru un apel care citește o pagină de magazin. Nu s-a citit și nu s-a taxat nimic. |
| 403 | — | Cererea a fost blocată de firewall înainte să ajungă la API — de obicei din cauza unui User-Agent generic, cum e Python-urllib. Trimite propriul User-Agent. |
| 404 | — | Resursa nu există, nu aparține contului tău sau calea nu e disponibilă prin API. |
| 409 | recommendation_moved · own_from_listing · invalid_state · webhook_secret_required | Conflict: prețul recomandat s-a schimbat (recommendation_moved), acțiunea e în altă stare (invalid_state), e nevoie de un secret de webhook sau operația nu se aplică acestui element. |
| 422 | — | Date invalide, de exemplu un URL fără http/https sau un câmp obligatoriu lipsă. detail enumeră câmpurile. |
| 429 | — | Mai mult de 60 de cereri pe minut pe cont (sau prea multe chei invalide). Aștepți câte secunde spune Retry-After. |
| 503 | credit_wallet_unavailable | Portofelul de credite nu poate fi citit acum. Reîncearcă mai târziu; nu s-a taxat nimic. |
Corpuri de eroare
json
{"detail": "Invalid or missing API key"}
{"detail": {"code": "plan_required", "message": "…"}}
{"detail": {"code": "insufficient_credits", "message": "…"}}
{"detail": [{"loc": ["body", "price_cents"], "msg": "…", "type": "…"}]}Exemple de cod
Aceeași citire în patru limbaje, cu pauză și reîncercare la 429, apoi trimiterea vânzărilor. Înlocuiește PUNE_AICI_CHEIA_TA cu cheia ta (sau setează variabila de mediu PRICING_API_KEY).
Node.js: citește produsele comparate
javascript
// Node.js 18+ (fetch is built in, no package needed)
const API_KEY = process.env.PRICING_API_KEY; // PUNE_AICI_CHEIA_TA
const BASE = "https://4pricing.net/api/v1/pricing";
async function call(path, options = {}) {
for (let attempt = 0; attempt < 3; attempt++) {
const res = await fetch(BASE + path, {
...options,
headers: { "X-API-Key": API_KEY, "Content-Type": "application/json", ...(options.headers || {}) },
});
if (res.status === 429) {
// Peste 60 de cereri/minut: aștepți câte secunde spune Retry-After, apoi reîncerci
const wait = Number(res.headers.get("Retry-After") || 60);
await new Promise((r) => setTimeout(r, wait * 1000));
continue;
}
if (!res.ok) throw new Error(res.status + " " + (await res.text()));
return res.status === 204 ? null : res.json();
}
throw new Error("rate limited");
}
async function main() {
// Produsele tale comparate: prețul tău, cel mai ieftin concurent, verdictul
const groups = await call("/groups/");
for (const g of groups) {
console.log(g.name, g.own_price_cents_effective, g.min_competitor_cents, g.verdict);
}
}
main().catch((err) => {
console.error(err.message);
process.exit(1);
});
Python: citește produsele comparate
python
# Python 3, standard library only
import json
import os
import time
import urllib.error
import urllib.request
API_KEY = os.environ["PRICING_API_KEY"] # PUNE_AICI_CHEIA_TA
BASE = "https://4pricing.net/api/v1/pricing"
def call(path, method="GET", payload=None):
data = json.dumps(payload).encode() if payload is not None else None
for _ in range(3):
req = urllib.request.Request(BASE + path, data=data, method=method, headers={
"X-API-Key": API_KEY, "Content-Type": "application/json", "Accept": "application/json",
# Pune-ți propriul User-Agent: cel implicit al unor biblioteci HTTP (Python urllib) e blocat cu 403
"User-Agent": "my-shop-integration/1.0",
})
try:
with urllib.request.urlopen(req, timeout=30) as res:
body = res.read()
return json.loads(body) if body else None
except urllib.error.HTTPError as err:
if err.code == 429:
# Peste 60 de cereri/minut: aștepți câte secunde spune Retry-After, apoi reîncerci
time.sleep(int(err.headers.get("Retry-After", "60")))
continue
raise
raise RuntimeError("rate limited")
# Produsele tale comparate: prețul tău, cel mai ieftin concurent, verdictul
for g in call("/groups/"):
print(g["name"], g["own_price_cents_effective"], g["min_competitor_cents"], g["verdict"])
curl: trimite vânzările zilnice
bash
curl -X POST https://4pricing.net/api/v1/pricing/sales/bulk \
-H "X-API-Key: PUNE_AICI_CHEIA_TA" \
-H "Content-Type: application/json" \
-d '{"rows":[{"sku":"TRICOU-ALB-M","day":"2026-10-12","units_sold":7,"revenue_cents":34993}]}'PHP: trimite vânzările zilnice
php
<?php
$apiKey = 'PUNE_AICI_CHEIA_TA';
// Vânzările zilnice pe produs (după sku sau group_id), până la 2.000 de rânduri pe cerere
$rows = [
['sku' => 'TRICOU-ALB-M', 'day' => '2026-10-12', 'units_sold' => 7, 'revenue_cents' => 34993],
];
$ch = curl_init('https://4pricing.net/api/v1/pricing/sales/bulk');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => 'my-shop-integration/1.0',
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . $apiKey, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['rows' => $rows]),
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE) . ' ' . $body; // 200 {"upserted": 1, "skipped": []}
Întrebări frecvente
Trebuie să fiu programator ca să folosesc API-ul 4pricing?
Nu. Integrarea rapidă îți dă un fișier PHP gata făcut: creezi o cheie în panou, o lipești în fișier, urci fișierul pe hosting și îl deschizi în browser. Pentru integrări mai adânci (un ERP, o platformă de magazin), un programator folosește lista de endpoint-uri și specificația OpenAPI.
Cât costă API-ul 4pricing?
API-ul e inclus în orice abonament 4pricing, cu aceleași limite ca în panou. Citirea datelor tale e gratuită; doar apelurile care citesc o pagină de magazin (adăugarea unui URL nou, reîmprospătarea, căutarea live, căutarea concurenței) consumă credite, la fel ca în panou.
Ce apeluri consumă credite?
POST /products/, POST /products/{product_id}/refresh, POST /groups/{group_id}/refresh, POST /groups/{group_id}/members cu un URL nou, POST /groups/{group_id}/suggestions și POST /search/. Dacă soldul nu ajunge, primești 402 insufficient_credits și nu se citește nimic.
Prețurile pe care le trimit prin API intră în limita de URL-uri a planului?
Nu. Se numără doar adresele pe care 4pricing le citește de pe o pagină — paginile produselor tale și fiecare pagină de concurent, separat. Prețurile trimise cu POST /own-products/ sau /own-products/bulk nu se numără nici ca URL-uri, nici ca credite.
Pot limita ce vede o cheie?
Nu, nu există scope-uri: o cheie vede și poate modifica tot contul tău 4pricing. Folosește câte o cheie separată pentru fiecare integrare și revocă orice cheie pe care nu o mai folosești; revocarea are efect imediat.
Ce se întâmplă dacă trimit mai mult de 60 de cereri pe minut?
Primești 429 cu antetul Retry-After. Limita e pe cont, împărțită între toate cheile tale. Aștepți câte secunde scrie în Retry-After și reîncerci; pentru multe produse folosește apelurile bulk (până la 2.000 de rânduri pe cerere).
Cum ajunge automat prețul recomandat în magazinul meu?
Legi grupul de magazin cu store_kind = webhook, setezi adresa și secretul de webhook și tratezi evenimentul price.apply: schimbi prețul în magazin, apoi apelezi POST /actions/{action_id}/confirm. Aprobarea unei schimbări de preț rămâne o decizie luată în panou.
Există un SDK sau un plugin gata făcut?
Nu există un SDK sau un plugin oficial. API-ul este REST simplu, cu JSON, deci merge din orice limbaj care poate face cereri HTTPS; specificația OpenAPI poate fi încărcată în unelte care generează automat un client.