Podłączenie własnych systemów

Cztery sposoby, na jakie Twoje ceny, możliwości produkcyjne i asortyment mogą trafić na ten rynek — wpisane ręcznie, wysłane przez Twój system, zapytane przez nasz albo odczytane ze sklepu, który już prowadzisz.

Cztery sposoby na obecność w katalogu

To równorzędne opcje, nie poziomy. Wybór zależy od tego, czy masz już system wart podłączenia, i od tego, jak często realnie zmienia się to, co sprzedajesz. Nikt nie jest niżej oceniany za ręczne wpisanie cennika.

DrogaIle kosztuje Cię uruchomienieKto pilnuje aktualnościKiedy się aktualizujeDla kogo
🖥️ Wpisujesz tutaj Nic. To formularz.Ty, ręcznieKiedy pamiętasz, żeby to poprawićWarsztat, którego cennik zmienia się dwa razy w roku. To także jedyna z czterech dróg, która działa dzisiaj.
📤 Wysyłasz do nas 🚧Jedno zadanie w Twoim systemie, które wysyła plikTy. Twój system pozostaje źródłem prawdy.Tak często, jak wyśleszMasz już system magazynowy albo ERP, a Twoje ceny zmieniają się szybciej, niż chciałbyś je przepisywać.
📥 Pytamy Ciebie 🚧Wystawiasz jeden endpoint, a my go wywołujemyTy, w ramach utrzymania własnej usługiPrzy każdym zamówieniu, w chwili zapytaniaTwoja cena naprawdę zależy od zlecenia — od nakładu, materiału, od tego, jak zapełniony masz przyszły tydzień.
🌐 Czytamy Twój sklep 🚧Wysyłasz nam link i odpowiadasz na kilka pytańMy — i będziemy się mylićKiedy następnym razem odczytamy stronęMasz już sklep i zerową ochotę na którąkolwiek z powyższych opcji.

Co byłoby wspólne dla każdej drogi przez API

🔑 Uwierzytelnianie

Propozycja: token bearer wydawany z Twojego panelu dostawcy i ograniczony do jednego konta, do którego należy — tego samego, na które już się logujesz, bo zostanie dostawcą nigdy nie tworzyło tu drugiego konta. Zapytania, które my wysyłamy do Ciebie, byłyby podpisywane w drugą stronę: HMAC surowej treści w nagłówku X-Personali-Signature, żebyś mógł potwierdzić, że zapytanie jest od nas, bez przechowywania przez nas Twojego hasła.

To, czy właściwy jest zwykły token, OAuth czy mutual TLS, jest naprawdę nierozstrzygnięte, a dostawca, który integrował się już z kilkoma rynkami, ma tu bardziej użyteczne zdanie niż my.

♻️ Wysłanie tego samego dwa razy

Zapisy katalogu to upsert po polu sku — Twoim własnym identyfikatorze pozycji, który przechowujemy i nigdy nie zmieniamy. Ponowne wysłanie tego samego sku edytuje tę pozycję, zamiast tworzyć drugą, więc powtórzenie synchronizacji przerwanej w połowie jest bezpieczne i nigdy nie musisz utrzymywać tabeli naszych identyfikatorów obok swoich.

Pozycja, której przestaniesz wysyłać, nie zostaje usunięta. Milczenie nie jest tu wycofaniem — to ta sama zasada, którą reszta tego rynku stosuje wobec dostawcy, który zostawił puste pole rozmiaru. Wycofanie pozycji to jawny status withdrawn, dzięki czemu obcięty plik nigdy po cichu nie opróżni Twojego katalogu.

⚠️ Gdy coś nie przejdzie walidacji

Per pozycja, nie per żądanie. Czterdzieści pozycji z dwoma błędnymi wierszami zapisze trzydzieści osiem i zgłosi te dwa, bo odrzucenie całego pliku z powodu jednej literówki zostawia Twój katalog nieaktualny, dopóki ktoś tego nie zauważy. Odpowiedź nazywa pozycję Twoim własnym sku, a pole jego ścieżką.

Wartość, której nie rozpoznajemy — materiał bez identyfikatora u nas, etap produkcji, którego nie prowadzimy — jest zgłaszana jako nierozpoznana, a nie pomijana. Pole po cichu zignorowane to dokładnie sposób, w jaki dostawca zaczyna wierzyć, że opublikował coś, czego nie opublikował.

📤 Droga 2 — wysyłanie katalogu do nas

Dwa wywołania. Jedno mówi, kim jesteś i ile bierzesz; drugie mówi, co wytwarzasz. Oba odzwierciedlają struktury, których ta aplikacja już używa wewnętrznie — dlatego pola wyglądają jak formularz zgłoszeniowy. Bo dokładnie tym są.

Krok 1 — Twój profil dostawcy

role i category decydują o tym, jakie zapytania w ogóle zobaczysz. price i turnaroundDays to Twoja stawka podstawowa. priceBreaks to Twoja drabinka ilościowa jako jawna lista progów, które sam wybrałeś, a nie krzywa dopasowana za Ciebie — progi producenta to decyzja biznesowa, którą potrafi uzasadnić, i wolimy opublikować Twoją niż wymyślić własną. city jest tym, co nadaje sens odbiorowi osobistemu i drukarniom w pobliżu.

sizesOffered, madeToMeasure i maxDimensionsCm to deklaracja Twoich możliwości: jakie rozmiary trzymasz lub szyjesz, czy w ogóle wykonasz rzecz na wymiar klienta i jaka jest największa rzecz, która mieści się w Twoim warsztacie. Pominięcie któregokolwiek z nich traktujemy jako brak deklaracji, nigdy jako odmowę — dostawca, który wypełnił mniej formularza, nie powinien być za to po cichu karany.

Wszystkie kwoty są w PLN za sztukę, bo każda cena w tej aplikacji taka jest. Zobacz pytania otwarte — to realne ograniczenie, a nie konwencja, z której jesteśmy zadowoleni.

role             artist | vendor | warehouse | service | seller
category         print | sew | assemble | engrave | transport     (role: vendor only)
sizesOffered     xs | s | m | l | xl | xxl | xxxl | one_size | s_m | l_xl
city             warszawa | krakow | lodz | wroclaw | poznan | gdansk | szczecin |
                 katowice | lublin | rzeszow | kyiv | lviv | odesa | berlin | praha |
                 vilnius | bratislava | amsterdam | paris | madrid
POST /v1/supplier/profile
Authorization: Bearer <your-token>
Content-Type: application/json

{
  "role": "vendor",
  "category": "print",
  "displayName": "PrintHouse Kraków",
  "avatarEmoji": "🖨️",
  "city": "krakow",
  "price": 24,
  "turnaroundDays": 3,
  "priceBreaks": [
    { "minQty": 25,  "unitPrice": 20 },
    { "minQty": 100, "unitPrice": 17 }
  ],
  "description": "DTG and 4-colour screen print. Under 25 pieces we run DTG; above that screen becomes cheaper and we will say so rather than quietly charge the DTG rate.",
  "sizesOffered": ["s", "m", "l", "xl", "xxl"],
  "madeToMeasure": false,
  "maxDimensionsCm": { "length": 200, "width": 120, "height": 60 }
}

Krok 2 — pozycje, które dostarczasz

Pozycja to tutaj jedna konkretna, zamawialna rzecz wewnątrz jednego z naszych typów produktu, a nie osobny typ produktu. Nasz Kubek to karta; Twój ceramiczny kubek 450 ml z podwójną ścianką to pozycja na tej karcie. Dlatego każda pozycja podaje productId, do którego należy.

priceModifier to kwota, o jaką Twoja pozycja jest droższa od najtańszej pozycji na tej samej karcie, w PLN za sztukę. Modyfikator, a nie pełna cena, i to celowo: druk czy szycie to ta sama praca niezależnie od tego, na której pozycji danego produktu są wykonywane, więc druga pełna cena musiałaby powtórzyć cały łańcuch i mogłaby potem być z nim sprzeczna. extraLeadDays to dni, które Twoja pozycja dokłada do harmonogramu — kubka toczonego ręcznie nie zdejmuje się z półki.

material i personalizations pochodzą z zamkniętych słowników, a nie z pola tekstowego, bo klienci po nich filtrują: „Bawełna organiczna” i „bawełna organiczna” stałyby się dwoma różnymi filtrami przy pierwszym wpisaniu przez dwie osoby. nameLocalized jest opcjonalne i warto je wysłać — wymyślona część marki w nazwie się nie tłumaczy, ale „Heavyweight” czy „Hand-Finished” to fakty o produkcie i pozostawienie ich po angielsku nic polskiemu kupującemu nie mówi.

stock to jedyne pole na tej stronie, które nie ma dziś odpowiednika nigdzie w aplikacji. Jest w przykładzie, bo prawdziwa integracja by je niosła; w pytaniach otwartych jest wyjaśnione, dlaczego nikt jeszcze nie potrafi powiedzieć, co by robiło.

productId        hoodie | tshirt | cap | tote_bag | mug | water_bottle | tumbler |
                 poster | canvas | stickers | cushion | wall_clock | blanket |
                 phone_case | keychain | pen | chair | bookshelf
material         cotton | organic_cotton | cotton_blend | fleece | wool | velvet |
                 canvas | recycled_polyester | recycled_plastic | silicone | leather |
                 ceramic | stainless_steel | brass | bamboo | oak | pine | paper |
                 fine_art_paper | vinyl
personalizations print | embroidery | engraving | foil | handpaint
status           listed | withdrawn
POST /v1/supplier/items
Authorization: Bearer <your-token>
Content-Type: application/json

{
  "items": [
    {
      "sku": "PH-MUG-450-DW",
      "productId": "mug",
      "name": "BrewLine Thermal 450",
      "nameLocalized": { "pl": "BrewLine Termiczny 450", "uk": "BrewLine Термо 450" },
      "material": "ceramic",
      "personalizations": ["print", "engraving"],
      "priceModifier": 14,
      "extraLeadDays": 2,
      "stock": 340,
      "status": "listed"
    },
    {
      "sku": "PH-TEE-ORG-XXXL",
      "productId": "tee",
      "name": "Organic Heavyweight Tee",
      "material": "organic-cotton",
      "personalizations": ["print"],
      "priceModifier": 9,
      "status": "listed"
    }
  ]
}

Co wraca

Status 207 z listą każdego wysłanego sku i tego, co się z nim stało. Odrzucony wiersz poniżej zawodzi celowo dwa razy: jedno pole to pomyłka o włos przy prawdziwym słowniku, drugie to nieistniejący identyfikator produktu. Oba podają alternatywy, bo błąd, który mówi tylko „nie”, kosztuje kogoś całe popołudnie.

HTTP/1.1 207 Multi-Status
Content-Type: application/json

{
  "accepted": 1,
  "rejected": 1,
  "results": [
    {
      "sku": "PH-MUG-450-DW",
      "status": "stored",
      "listedAs": "mug / BrewLine Thermal 450"
    },
    {
      "sku": "PH-TEE-ORG-XXXL",
      "status": "rejected",
      "errors": [
        {
          "field": "productId",
          "code": "unknown_product",
          "message": "No product 'tee'. Apparel ids are hoodie, tshirt, cap, tote_bag.",
          "didYouMean": "tshirt"
        },
        {
          "field": "material",
          "code": "unknown_value",
          "message": "'organic-cotton' is not a material id.",
          "didYouMean": "organic_cotton"
        }
      ]
    }
  ]
}

📥 Droga 3 — wysyłanie zapytań do Ciebie

Odbicie lustrzane i ciekawszy przypadek. Zamiast publikować cennik i pozwalać naszej arytmetyce go stosować, rejestrujesz adres URL, a my pytamy, ile kosztuje jedno konkretne zlecenie, w chwili gdy ktoś go chce. Wysyłamy zapytanie ofertowe, na którym ten rynek już działa; oczekujemy w zamian oferty, czyli dokładnie tego, co dostawca odpowiadający ręcznie wpisuje w panelu w formularz.

Zapytanie o wycenę

POST https://your-system.example/personali/quote
X-Personali-Signature: sha256=9f86d081884c7d659a2feaa0c55ad015
Content-Type: application/json

{
  "requestId": "req_8f2c41",
  "cartId": "cart_31a9d0",
  "target": { "kind": "step", "itemId": "item_7d1", "stepId": "print" },
  "stepLabelKey": "step_label_print",
  "quantity": 120,
  "bulkTargetQuantity": 2500,

  "item": {
    "productId": "tshirt",
    "specificProductId": "tshirt-std",
    "supplierSku": "PH-TEE-ORG-L",
    "size": "l",
    "material": "organic_cotton",
    "personalizations": ["print"]
  },
  "deliverTo": { "city": "warszawa" },
  "respondBy": "2026-08-07T09:41:12Z"
}

Uczciwa uwaga do tego ładunku: requestId, target, stepLabelKey, quantity i bulkTargetQuantity to pola, które zapytanie ofertowe w aplikacji już niesie. item, deliverTo i respondBy — nie. Dostawca patrzący na panel widzi pozycję na ekranie przed sobą, a maszyna nie, więc webhook musi powiedzieć na głos to, co strona jedynie pokazuje. Oznaczone osobno, a nie wtopione w resztę, żeby nic tutaj nie czytało się jako dokumentacja czegoś, co istnieje.

Twoja odpowiedź

Jedna pułapka warta głośnego powiedzenia: price dotyczy całego zapytania — wszystkich 120 sztuk — a nie jednej sztuki. Taka konwencja obowiązuje w reszcie tego rynku dla ceny etapu, a maszyna, która przez pomyłkę wyceni za sztukę, zaniży swoją ofertę o dwa rzędy wielkości. bulkUnitPrice to cena za sztukę przy docelowym nakładzie i to właśnie ta liczba naprawdę decyduje dla kupującego firmowego, gdy w grę wchodzi partia próbna.

HTTP/1.1 200 OK
Content-Type: application/json

{
  "proposals": [
    {
      "price": 1980,
      "turnaroundDays": 4,
      "bulkUnitPrice": 12.4,
      "note": "Screen print, 3 colours. The 2 500 rate assumes one artwork, no colour change."
    }
  ]
}

Odmowa jest pełnoprawną odpowiedzią

Pusta tablica proposals znaczy „nie to zlecenie”. Dla obu stron jest to lepsze niż cena, której wolałbyś nie honorować, i jest to dokładnie to, co dostawca robi w panelu codziennie, po prostu nie składając oferty. To wycena, a nie zamówienie: klient nadal porówna ją ze wszystkimi innymi i może wybrać inny warsztat.

HTTP/1.1 200 OK
Content-Type: application/json

{ "proposals": [] }

Jeśli Twój endpoint nie odpowiada

Wycena, której nie uda nam się uzyskać w ciągu kilku sekund, cofnęłaby się do Twojego opublikowanego cennika, jeśli go masz, i do niczego, jeśli nie masz — nie można zostawić klienta czekającego na serwer, który nie odpowiada. Jaki powinien być limit czasu i czy endpoint, który stale się nie wyrabia, powinien sam się wyciszać do czasu naprawy, pozostaje nierozstrzygnięte.

Albo pozwól nam też pobierać katalog

Jeśli to zaplanowane wysyłanie jest dla Ciebie tą niewygodną częścią, ta sama tablica items z drogi 2 może być czymś, co pobieramy, zamiast czymś, co wysyłasz. Jedno GET, ta sama treść, te same zasady upsertu. Która z dwóch dróg jest mniejszą pracą, zależy wyłącznie od Twoich systemów, więc oferujemy obie, zamiast nazywać jedną tą właściwą.

GET https://your-system.example/personali/catalogue
X-Personali-Signature: sha256=1b4f0e9851971998e732078544c96b36
Accept: application/json

→ 200 OK, with a body identical to the "items" payload in route 2 above.

🌐 Czytamy Twój sklep

Co naprawdę oznacza czytanie Twojej strony

Nie da się rzetelnie odczytać dowolnego sklepu i nie będziemy nazywać tego synchronizacją. Cokolwiek tu powstanie, będzie po części parserem, a po części człowiekiem: pierwsze przejście po Twoich stronach, potem ktoś po naszej stronie dopasowuje znalezione dane do naszych kategorii, etapów produkcji, materiałów i rozmiarów, a potem Ty zatwierdzasz wynik, zanim cokolwiek trafi na żywo.

Oferta utworzona w ten sposób nadal jest Twoja. Możesz ją poprawić w zwykłym formularzu, a poprawka zawsze wygrywa z tym, co odczytamy później — dostawca, który naprawi błędną cenę, nie powinien patrzeć, jak wraca ona w nocy.

Czego byśmy od Ciebie potrzebowali

  • Adresu sklepu i zgody na jego odczytywanie.
  • Informacji, które z naszych etapów produkcji faktycznie wykonujesz — strona sklepu pokazuje gotowe wyroby i nic nie mówi o tym, która część łańcucha należy do Ciebie.
  • Czy pokazane ceny zawierają VAT, bo strona rzadko to podaje, a błędne założenie to pomyłka o 23%.
  • Osoby do kontaktu, gdy dopasowanie będzie błędne — a czasem będzie.

Świadomie nierozstrzygnięte

Nazwanie tych rzeczy jest właśnie powodem wcześniejszej publikacji. Każda z nich to decyzja, którą należy podjąć z dostawcami, a nie im przedstawić.

  • Limity zapytań i rozmiar ładunku. Nic nie jest ustalone. Dostawca z czterdziestoma tysiącami pozycji i dostawca z dwunastoma są tu równie prawdopodobni, a wybranie rozmiaru strony, zanim pojawi się którykolwiek z nich, byłoby tylko liczbą do późniejszego wycofania.
  • Stany magazynowe. Produkcja na zamówienie w ogóle nie ma stanu, a jedyne miejsce, w którym ten rynek sprzedaje wyroby gotowe, śledzi dostępność jako rezerwację na ofercie, a nie jako licznik, który ktoś wysyła. Czy przesłany stan ma się zmniejszać przy zamówieniu, sam wygasać, czy być wyłącznie orientacyjny — na to nie ma jeszcze odpowiedzi.
  • Waluta. Każda cena w tej aplikacji jest w PLN i nie ma w niej nigdzie pola waluty. Ukraiński albo niemiecki dostawca integrujący się przez API to dokładnie ten przypadek, który to łamie, i nie da się tego naprawić samym dodaniem pola bez rozstrzygnięcia, kto przelicza, po jakim kursie i kto ponosi różnicę między wyceną a fakturą.
  • Sam model uwierzytelniania oraz to, jak rotuje się lub unieważnia token, gdy ktoś odchodzi z Twojej firmy.
  • Ponowienia i powtórki przy zapytaniach, które my wysyłamy do Ciebie: ile razy, w jakich odstępach i czy dwukrotna odpowiedź na to samo zapytanie musi być bezpieczna.
  • Czyj zapis wygrywa. Jeśli wyślesz cenę, a potem poprawisz ją w formularzu, jedno z dwojga musi przegrać. Nasz odruch mówi, że wygrywa ostatni zapis, niezależnie od tego, którą drogą przyszedł — ale dostawca, którego ERP co noc po cichu nadpisuje ręczną poprawkę, słusznie by tego nie znosił, więc sprawa nie jest zamknięta.
  • Nie ma środowiska testowego, nie ma testowych danych logowania i nie ma schematu do odczytu maszynowego, bo nie ma serwera. Kiedy będzie, środowisko testowe pojawi się przed dokumentacją, a nie po niej.
  • Parser z drogi 4 nie istnieje nawet w zarysie. To, ile z niego da się zautomatyzować, a ile zawsze będzie człowiekiem czytającym Twoje strony, jest pytaniem rozstrzygającym, czy ta droga w ogóle zostanie zaoferowana.

Co realnie możesz zrobić dzisiaj

Złóż zgłoszenie przez formularz, tak jak wszyscy. W polu opisu napisz, która z tych czterech dróg Cię interesuje i jaki masz system — to pole jest tekstowe właśnie dlatego, że żadne pole ustrukturyzowane nie pomieściłoby takiej odpowiedzi. Trafia do człowieka i jest jedyną rzeczą na tej stronie, która w ogóle gdziekolwiek trafia.