Za razvijalce

    Javni API in webhooki

    Povežite ProEntry s svojo spletno stranjo, Zapierjem ali internim sistemom. Prek REST API-ja berete delovni čas, storitve, račune in rezervacije ter ustvarjate povpraševanja v CRM; z izhodnimi webhooki pa vas ProEntry sam obvesti o novih povpraševanjih, rezervacijah in plačilih.

    API dostop je na voljo v paketih Pro, Ekipa in Premium.

    Avtentikacija

    API ključ ustvarite v aplikaciji: Nastavitve → API dostop. Ključe lahko ustvarja in prekliče lastnik ali vodja podjetja; na podjetje je lahko aktivnih največ 10 ključev. Ključ ima obliko pe_live_ + 32 znakov in se v celoti prikaže samo enkrat, ob kreaciji — shranite ga varno, kasneje je viden le začetek ključa.

    Ključ pošljete z vsakim zahtevkom na enega od dveh načinov (Bearer ima prednost):

    Authorization: Bearer pe_live_...
    # ali
    X-Api-Key: pe_live_...

    Vsak ključ dobi ob kreaciji podmnožico scope-ov (npr. invoices:read) — endpoint brez ustreznega scope-a vrne 403 insufficient_scope. Preklican ključ ali deaktivirano podjetje vrne 401 invalid_key.

    Endpointi

    Osnovni URL: https://<project>.supabase.co/functions/v1/api-v1 (v primerih spodaj $BASE). Uspešen odgovor ima obliko {"data": [...]}; seznami podpirajo paginacijo z ?limit= (1–100, privzeto 50) in ?offset=.

    MetodaPotScopeOpis
    GET/work-hourswork_hours:readDelovni čas podjetja (7 vrstic, day_of_week 0 = ponedeljek … 6 = nedelja).
    GET/servicesservices:readAktivne storitve: id, name, price, duration_minutes.
    POST/inquiriesinquiries:writeUstvari povpraševanje v CRM (brez captche — zahtevek je avtenticiran s ključem).
    GET/invoicesinvoices:readGlave dokumentov (brez postavk): številka, datumi, znesek, status, stranka, plačilni podatki. Filtri: status, document_type, from/to, external_ref.
    POST/ordersorders:writeProdajno naročilo iz zunanjega sistema (spletna trgovina): najde ali ustvari stranko, po želji projekt, in izda predračun s sklicem SI00 in UPN QR; strežniški PDF (pdf_url, 30 dni) in ob send_email pošiljanje stranki. Zahteva funkcijo paketa ORDERS_API.
    GET/bookingsbookings:readJavne rezervacije s podatki o stranki in storitvah. Filtra from/to (po datumu rezervacije).
    POST/sales-orderssales_orders:writeVeleprodajni nalog + predračun za cel nalog (sklic SI00, UPN QR); odgovor vsebuje rezervacije (reservation) in backorderje (backorders). SKU se razreši v artikel zaloge.
    GET/sales-orderssales_orders:readNalogi s postavkami (naročeno/rezervirano/odpremljeno/backorder) in plačilnim stanjem predračuna. Filter external_ref.
    GET/sales-orders/{id}sales_orders:readEn nalog s postavkami, predračunom in odpremami.
    POST/sales-orders/{id}/fulfillmentssales_orders:writeDelna ali celotna odprema (dobavnica). Idempotentno po external_ref ali Idempotency-Key. Zahteva plačan predračun (ali ročno odobritev) in nalog brez kreditne blokade.
    POST/sales-orders/{id}/cancelsales_orders:writePreklic neodpremljenega naloga z neplačanim predračunom: sprosti rezervacije in prekliče predračun; external_ref je nato prost.
    POST/sales-orders/{id}/backorders/resolvesales_orders:writePo prispeli zalogi rezervira backorder vrstice naloga (najstarejši backorder prvi). Zahteva funkcijo BACKORDERS.
    GET/inventoryinventory:readRazpoložljiva zaloga po SKU (inventory_item_id, sku, available). Zahteva funkcijo STOCK_RESERVATIONS.
    GET/pricesprices:readCena kupca za en artikel (sku ali inventory_item_id, qty): kupčev cenik > kampanja > skupina kupcev > osnovni cenik > cena artikla; vrne tudi vir cene. Zahteva funkcijo PRICE_LISTS (add-on Veleprodaja Plus).
    POST/prices/resolveprices:readSeznam cen za katalog portala (do 200 artiklov v enem klicu) za kupca (client_id, davčna ali e-pošta) ali anonimno. Zahteva funkcijo PRICE_LISTS (add-on Veleprodaja Plus).

    Primer: delovni čas

    curl -H "Authorization: Bearer pe_live_..." \
      "$BASE/work-hours"
    
    # Odgovor:
    {"data": [{"day_of_week": 0, "is_open": true, "open_time": "08:00:00",
      "close_time": "20:00:00", "break_start": "12:00:00", "break_end": "13:00:00"}]}

    Primer: novo povpraševanje

    Obvezni polji sta name in description, plus vsaj eden od phone / email. Opcijsko: serviceType, source in gdprConsent (ob true se zabeleži čas privolitve).

    curl -X POST -H "Authorization: Bearer pe_live_..." \
      -H "Content-Type: application/json" \
      -d '{"name":"Janez Novak","phone":"041123456","description":"Ponudba za ograjo","gdprConsent":true}' \
      "$BASE/inquiries"
    
    # Odgovor (201):
    {"data": {"id": "...", "created_at": "..."}}

    Primer: naročilo iz spletne trgovine → predračun

    Strežnik zneske preračuna sam (odjemalčevi totals so le kontrola), stranko poišče po DDV številki ali e-pošti, predračun dobi številko iz vašega številčenja. Isti Idempotency-Key z istim telesom vrne isti dokument (brez podvajanja). Celotna pogodba: docs/specs/2026-09-orders-api.md.

    curl -X POST -H "Authorization: Bearer pe_live_..." \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: order-8f1c2d3e-v1" \
      -d '{
        "external_ref": "8f1c2d3e", "external_source": "trgovina",
        "client": { "name": "Avtohiša Novak d.o.o.", "customer_type": "business",
                    "vat_number": "SI12345678", "email": "narocila@novak.si",
                    "address": "Cesta 1", "postal_code": "1000", "city": "Ljubljana" },
        "items": [ { "description": "Kartonska podloga", "quantity": 100, "unit": "kos",
                     "unit_price": 0.85, "vat_rate": 22, "item_type": "material" } ],
        "create_project": true
      }' \
      "$BASE/orders"
    
    # Odgovor (201):
    {"data": {"invoice_id": "...", "document_number": "PF-2026/09/012", "status": "issued",
      "issue_date": "2026-09-15", "due_date": "2026-09-23", "valid_until": "2026-09-30",
      "totals": {"subtotal": 85.00, "total_vat": 18.70, "total": 103.70, "currency": "EUR"},
      "payment": {"iban": "SI56...", "reference": "SI00 0202-6090-12",
                  "purpose": "Plačilo po predračunu PF-2026/09/012", "amount": 103.70,
                  "upn_qr_payload": "UPNQR\n..."}}}

    Rate limiti

    60 / min

    Minutni limit na ključ. Ob prekoračitvi: 429, Retry-After: 60, koda rate_limited.

    1000 / dan

    Dnevni limit na ključ. Ob prekoračitvi: 429, Retry-After: 3600, koda rate_limited_day.

    V kvoto se štejejo samo postreženi zahtevki — zavrnjeni z 429 se ne štejejo.

    Webhooki

    ProEntry lahko vaš sistem obvešča o dogodkih s HTTP POST klicem na vaš https:// URL (privatni in lokalni naslovi so blokirani). Ob kreiranju naročnine prejmete secret oblike pe_whsec_<32 hex znakov> — prikazan je samo enkrat in ga kasneje ni mogoče ponovno prebrati. Vaš endpoint mora v 10 sekundah vrniti status 2xx; redirectom ne sledimo.

    Dogodki

    DogodekOpis
    inquiry.createdNovo povpraševanje (ime, kontakt, opis, vir).
    inquiry.convertedPovpraševanje pretvorjeno v stranko: client_id, external_source/external_ref (portalni linkage), vir, converted_at, ime, e-pošta.
    booking.createdNova javna rezervacija (termin, storitve, cena, stranka).
    booking.cancelledPreklic rezervacije (termin, čas in razlog preklica).
    invoice.paidRačun označen kot plačan (številka, znesek, stranka, datum plačila). Samo fiskalni dokumenti — ne za predračune/ponudbe.
    proforma.paidPredračun v celoti plačan (evidentiran prejeti avans): številka, external_source/external_ref, znesek, valuta, paid_at, način plačila, advance_payment_id.
    proforma.createdNov predračun (tudi iz POST /orders): številka, external_source/external_ref, znesek, roki, source_channel.
    inventory.updatedSprememba razpoložljive zaloge (nivo A): sku, available, updated_at.
    sales_order.cancelledVeleprodajni nalog preklican: external_source/external_ref, razlog, število sproščenih rezervacij, stanje vrstic.
    sales_order.fulfilledOdprema naloga (complete = celoten nalog odpremljen): številka dobavnice, odpremljene vrstice, stanje vrstic.
    sales_order.backorderedVrstice naloga brez zadostne zaloge so v backorderju (le zalogovni artikli).
    pingTestni dogodek ob preizkusu naročnine — podpisan enako kot pravi dogodki.

    Oblika dostave

    Vsaka dostava je POST z JSON ovojnico. Isti dogodek lahko ob retryju prejmete večkrat — deduplicirajte po id dostave. Vrstni red dostav ni zagotovljen.

    POST <vaš URL>
    Content-Type: application/json
    X-ProEntry-Event: inquiry.created
    X-ProEntry-Delivery: 9c5e6d1e-...
    X-ProEntry-Signature: t=1755856800,v1=5f8a...
    User-Agent: ProEntry-Webhooks/1.0
    
    {
      "id": "9c5e6d1e-...",          // unikaten ID dostave (za deduplikacijo)
      "event": "inquiry.created",    // tip dogodka
      "created_at": "2026-08-22T10:00:00Z",
      "data": { ... }                // payload dogodka
    }

    Preverjanje podpisa

    Header X-ProEntry-Signature ima obliko t=<unix_ts>,v1=<hex>, kjer je v1 HMAC-SHA256 s secretom nad nizom "<unix_ts>.<surovo telo zahtevka>". Podpis vedno preverite nad surovim telesom (pred JSON parse), primerjajte s konstantno-časovno primerjavo in zavrnite zahtevke, kjer je razlika med t in trenutnim časom večja od npr. 5 minut (zaščita pred replay napadi).

    const crypto = require("node:crypto");
    
    function verifyProEntrySignature(rawBody, signatureHeader, secret, toleranceSec = 300) {
      const parts = Object.fromEntries(
        signatureHeader.split(",").map((p) => p.split("=", 2))
      );
      const ts = parseInt(parts.t, 10);
      if (!ts || !parts.v1) return false;
      if (Math.abs(Date.now() / 1000 - ts) > toleranceSec) return false;
    
      const expected = crypto
        .createHmac("sha256", secret)
        .update(`${ts}.${rawBody}`)
        .digest("hex");
      const a = Buffer.from(expected, "hex");
      const b = Buffer.from(parts.v1, "hex");
      return a.length === b.length && crypto.timingSafeEqual(a, b);
    }
    
    // Express: uporabite express.raw({ type: "application/json" }), da dobite surovo telo
    app.post("/webhooks/proentry", express.raw({ type: "application/json" }), (req, res) => {
      const ok = verifyProEntrySignature(
        req.body.toString("utf8"),
        req.get("X-ProEntry-Signature") || "",
        process.env.PROENTRY_WEBHOOK_SECRET
      );
      if (!ok) return res.status(401).end();
      const event = JSON.parse(req.body);
      // ... obdelava; deduplicirajte po event.id
      res.status(200).end();
    });

    Retry politika

    • Dostave obdeluje worker, ki teče vsako minuto — prva dostava tipično do ~1 minuto po dogodku.
    • Neuspela dostava (ne-2xx, timeout, napaka povezave) se ponovi z eksponentnim backoffom min(1 min × 2^poskus, 1 h): ~2 min, 4 min, 8 min, 16 min, 32 min, nato 1 h.
    • Največ 8 poskusov na dostavo; potem je dostava označena kot neuspešna in se ne ponavlja več.
    • Po 20 zaporednih neuspelih poskusih se naročnina samodejno deaktivira. Po odpravi napake jo znova vklopite; dogodki, nastali med deaktivacijo, se ne dostavijo za nazaj.

    Napake

    Vsaka napaka API-ja vrne JSON telo z opisom in strojno berljivo kodo:

    {"error": "<sporočilo>", "code": "<koda>"}
    StatusKodaKdaj
    401invalid_keyManjkajoč, neveljaven ali preklican ključ; tudi deaktivirano podjetje.
    403insufficient_scopeKljuč nima scope-a, ki ga endpoint zahteva.
    404not_foundNeznana pot.
    405method_not_allowedNapačna HTTP metoda za endpoint.
    429rate_limited / rate_limited_dayPresežen minutni oz. dnevni limit — glej Rate limiti.
    400validacijske kodeNeveljavno telo zahtevka (npr. manjkajoča obvezna polja pri POST /inquiries; validation_error s poljem field pri POST /orders).
    402iban_missingPOST /orders: podjetje nima vpisanega IBAN-a, zato predračuna s plačilnimi podatki ni mogoče izdati.
    403upgrade_requiredPOST /orders: paket ne vključuje funkcije ORDERS_API.
    409idempotency_conflict / external_ref_existsPOST /orders: isti Idempotency-Key z drugim telesom oz. odprt dokument za to naročilo že obstaja (telo vsebuje invoice_id).
    422totals_mismatch / vat_invalidPOST /orders: podani zneski odstopajo od strežniškega izračuna oz. DDV stopnje niso skladne z ZDDV-1.
    409payment_required / credit_hold / cancel_blocked / invalid_statusVeleprodaja: odprema pred plačilom predračuna, kreditna blokada, preklic plačanega/odpremljenega naloga oz. nedovoljen status naloga.
    422over_fulfillment / line_not_in_orderOdprema: količina presega naročeno oz. vrstica ne pripada nalogu.
    501not_implementedEndpoint potrebuje posodobitev baze, ki na tem okolju še ni aplicirana.
    503not_readyEndpoint na tem okolju še ni omogočen — poskusite kasneje.
    500server_errorNapaka na strežniku — zahtevek ponovite kasneje.

    Pripravljen za začetek?

    Preizkusite ProEntry brezplačno in odkrijte, kako lahko poenostavite svoje poslovanje.

    Pišite nam

    Kontakt

    E-pošta
    info@proentry.si
    Delovni čas
    Pon - Pet: 8:00 - 16:00