Dezvoltatori

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.

De ce ai nevoie
  • 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.
  1. Pasul 1: 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.

    Deschide API & chei în panou →

  2. Pasul 2: 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.
  3. Pasul 3: 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âmpCe înseamnă
skucodul produsului la tine; 4pricing găsește produsul după el sau îl creează prima dată
namenumele produsului, așa cum vrei să-l vezi în 4pricing
urlopțional: adresa paginii produsului din magazinul tău (nu este citită niciodată)
price_centsprețul înmulțit cu 100, fără zecimale: 49,99 → 4999
currencycodul 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"}]}'
Păstrează cheia în siguranță
  • 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 monitorizateFiecare 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 concurenteNumărul de site-uri (domenii) diferite ale concurenților pe care îi monitorizezi.
Intervalul minim de verificareCâ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ăEndpointCe faceCost
POST/groups/{group_id}/membersAdaugă un URL în grup, după product_id sau dintr-o adresă nouă (o adresă nouă e citită imediat)Credite
POST/groups/{group_id}/refreshVerifică acum toate URL-urile grupuluiCredite
POST/groups/{group_id}/suggestions„Caută concurența”: URL-uri de concurenți propuse, cu scor și motivCredite
POST/products/Începe monitorizarea unui URL (url, name, role own sau competitor) — e citit imediatCredite
POST/products/{product_id}/refreshVerifică 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ăEndpointCe faceCost
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/bulkTrimite 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ăEndpointCe faceCost
GET/groups/Toate produsele comparate, fiecare cu prețul tău, cel mai mic preț al concurenței, verdictul și prețul recomandatGratuit
POST/groups/Creează un produs comparat (nume, sku, prețul tău, cost, strategie de preț)Gratuit
GET/groups/{group_id}Un produs comparatGratuit
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 monitorizateGratuit
GET/groups/{group_id}/comparisonPoziția ta față de fiecare concurent, cu prețul recomandat și felul în care a fost calculatGratuit
GET/groups/{group_id}/historyIstoricul de preț al fiecărui URL din grupGratuit
GET/groups/seriesPoziția ta pe piață, zi cu zi, pe toate produseleGratuit
POST/groups/{group_id}/membersAdaugă 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}/refreshVerifică acum toate URL-urile grupuluiCredite
POST/groups/{group_id}/apply-recommendedFace 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/sourcesSursele disponibile pentru „Caută concurența”Gratuit
POST/groups/{group_id}/suggestions„Caută concurența”: URL-uri de concurenți propuse, cu scor și motivCredite

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ăEndpointCe faceCost
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 imediatCredite
POST/products/importAdaugă multe URL-uri deodată; sunt citite mai târziu, de monitorizarea programatăGratuit
GET/products/exportExport CSV cu URL-urile activeGratuit
GET/products/{product_id}Un URL monitorizat, cu ultimul prețGratuit
PATCH/products/{product_id}Schimbă numele, categoria sau intervalul de verificareGratuit
DELETE/products/{product_id}Oprește monitorizarea unui URL (rămâne păstrat ca inactiv)Gratuit
POST/products/{product_id}/refreshVerifică acum prețul acestui URL (respectând intervalul minim al planului)Credite
GET/products/{product_id}/historyIstoricul de preț al unui URLGratuit
GET/products/sparklinesUn istoric scurt de preț pentru mai multe URL-uri deodatăGratuit
GET/products/price-changesURL-urile al căror preț s-a schimbat la ultima verificareGratuit

Afli chiar acum prețul unui produs la retaileri.

MetodăEndpointCe faceCost
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ăEndpointCe faceCost
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}/rearmRearmează o alertă declanșatăGratuit
GET/alerts/eventsInbox-ul alertelor declanșateGratuit
GET/alerts/events/unread-countCâte alerte necitite aiGratuit
POST/alerts/events/read-allMarchează alertele ca citite (opțional doar pe cele de dinaintea unui moment)Gratuit
POST/alerts/events/{event_id}/readMarchează o alertă ca citităGratuit

Credite

Soldul, prognoza și fiecare mișcare de credite.

MetodăEndpointCe faceCost
GET/credits/Soldul de credite și prognoza de consumGratuit
GET/credits/ledgerFiecare mișcare de crediteGratuit
GET/credits/by-productCreditele 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ăEndpointCe faceCost
POST/sales/bulkVâ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 suprascrieGratuit

Insight-uri, oportunități și briefing

Ce s-a schimbat, de ce contează și ce ai de făcut — plus briefingul zilnic.

MetodăEndpointCe faceCost
GET/insights/Insight-uri cu dovezi și propuneri de preț (X-Total-Count)Gratuit
GET/insights/{insight_id}Un insight în detaliuGratuit
POST/insights/{insight_id}/dismissIgnoră un insightGratuit
GET/opportunities/Sumarul oportunităților: valoarea din vânzările tale, separată de estimarea de piațăGratuit
GET/briefings/Istoricul briefingurilor zilniceGratuit
GET/briefings/latestUltimul briefing zilnicGratuit
POST/briefings/generateReconstruieș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ăEndpointCe faceCost
GET/actions/Acțiunile de preț: propuse, trimise, aplicate, anulateGratuit
POST/actions/{action_id}/confirmConfirmă că sistemul tău a aplicat un preț primit prin price.apply sau price.revertGratuit

Setări

Setările contului, inclusiv adresa și secretul webhook-ului.

MetodăEndpointCe faceCost
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_enabledGratuit

Statistici

Situația contului și starea verificărilor de preț.

MetodăEndpointCe faceCost
GET/stats/Statistici de cont: URL-uri, alerte, schimbări în 24 de ore, grupuriGratuit
GET/stats/scrapersStarea verificărilor de preț, pe retaileriGratuit

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:

CaleDe 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 · revertAprobarea, 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/runAcț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

EvenimentCe înseamnă
alertS-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.applyUn 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.revertRevenirea la prețul anterior. Același corp ca price.apply; îl aplici și îl confirmi la fel.
briefing.readyBriefingul 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

AntetCe înseamnă
X-4Pricing-TimestampMomentul trimiterii, în secunde Unix. Respinge cererile prea vechi (de exemplu peste 5 minute).
X-4Pricing-Signaturesha256= 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-Typeapplication/json; charset=utf-8
User-Agent4pricing-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.

StatusCodCe înseamnă
401—Cheia API lipsește, e greșită sau a fost revocată.
402plan_requiredContul nu are un abonament 4pricing activ.
402plan_limitS-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.
402insufficient_creditsNu 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.
409recommendation_moved · own_from_listing · invalid_state · webhook_secret_requiredConflict: 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.
503credit_wallet_unavailablePortofelul 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.