Optima API Moduł: Logistyka (Handel / Magazyn)

Towary w Comarch Optima przez REST API — endpointy, pola, stany, sync

Kartoteka towarów to centralny zasób w każdej integracji Comarch Optima z e-commerce, marketplace czy hurtownią danych. Optima API udostępnia 25 endpointów pokrywających pełne CRUD, stany magazynowe per magazyn, cenniki, atrybuty (również zależne od kontrahenta — B2B), dodatkowe jednostki miary oraz incremental sync z kursorem dla efektywnej synchronizacji przyrostowej.

· Optima API · Cennik

To fragment dokumentacji

Opisuje wybraną encję z polami COM i pułapkami wdrożeniowymi. Pełna dokumentacja 976 endpointów (Swagger / OpenAPI) dostarczana jest razem z Optima API. Zobacz wszystkie opisane encje →

Lista endpointów REST

24 endpointów HTTP/JSON. Każdy przechodzi przez walidację COM Comarch — taka sama walidacja jak przy ręcznej pracy w Optimie.

Metoda Endpoint Opis
GET /api/towary/{kod} Pobierz towar po unikalnym kodzie. Pełne dane: pola, ceny, atrybuty.
GET /api/towary/id/{id} Pobierz towar po wewnętrznym ID Optimy.
GET /api/towary/wyszukaj Wyszukiwanie po fragmentach tekstu: nazwa (LIKE %x%), kod i numerKat (LIKE x%), EAN (exact). Flaga truncated informuje o ograniczeniu wyników.
POST /api/towary/ Utwórz nowy towar. Walidacja VAT, kod unikalny, powiązania z grupą i cennikiem przez Optimę.
POST /api/towary/batch batch Utwórz wiele towarów jednym żądaniem (max 100) — jedna sesja, jeden zapis.
POST /api/towary/batch/read batch Pobierz wiele towarów po kodach (max 500). Nieznane kody pomijane bez błędu.
PUT /api/towary/{kod} Aktualizuj towar (partial update). null = bez zmian, "" = wyczyść pole.
PUT /api/towary/batch/update batch Aktualizuj wiele towarów jednym żądaniem (max 100).
DELETE /api/towary/{kod} Usuń towar. 204 po weryfikacji zapisu potwierdzającej brak rekordu.
GET /api/sync/towary Incremental sync — zwraca towary zmodyfikowane od podanego timestampu, z kursorem dla kolejnych stron.
GET /api/towary/{kod}/stany Stany magazynowe per magazyn + opcjonalny agregat. Dostepne = Ilosc - Rezerwacje - Braki. Read-only.
GET /api/towary/id/{id}/stany Stany magazynowe po ID towaru.
POST /api/towary/stany/batch/read batch Stany magazynowe dla wielu towarów naraz (max 500). Idealny dla synchronizacji do sklepu.
GET /api/towary/{kod}/atrybuty Atrybuty towaru.
PUT /api/towary/{kod}/atrybuty Ustaw wartość atrybutu (create lub update).
PUT /api/towary/{kod}/atrybuty/batch batch Ustaw wiele atrybutów w jednej sesji.
DELETE /api/towary/{kod}/atrybuty/{atrybutKod} Usuń atrybut.
POST /api/towary/atrybuty/batch batch Atrybuty wielu towarów po ID (max 500).
POST /api/towary/atrybuty/batch/read batch Atrybuty wielu towarów po kodach (max 500).
GET /api/towary/{kod}/atrybuty/{atrybutKod}/kontrahenci Atrybuty zależne od kontrahenta (B2B) — różne wartości dla różnych klientów.
PUT /api/towary/{kod}/atrybuty/{atrybutKod}/kontrahenci Ustaw wartość atrybutu specyficzną dla kontrahenta.
DELETE /api/towary/{kod}/atrybuty/{atrybutKod}/kontrahenci/{kontrahentKod} Usuń atrybut zależny od kontrahenta.
POST /api/towary/{kod}/jednostki-miary Dodaj pomocniczą jednostkę miary (np. opakowanie = 10 szt).
DELETE /api/towary/{kod}/jednostki-miary/{jm} Usuń pomocniczą jednostkę miary.
Towary w Comarch Optima przez REST API

Co to jest towar w Comarch Optima

Kartoteka towarów w Optimie (Handel → Towary) przechowuje wszystko, co firma sprzedaje, kupuje lub wykorzystuje — fizyczne towary, usługi, produkty złożone z komponentów.

Optima API udostępnia 25 endpointów pokrywających:

  • CRUD pojedynczo i batch (do 100 create/update, do 500 read),
  • stany magazynowe per magazyn + agregaty,
  • cenniki (pobierane razem z towarem albo osobno przez endpoint cenników),
  • atrybuty standardowe i zależne od kontrahenta (B2B pricing),
  • dodatkowe jednostki miary (opakowania, palety),
  • incremental sync z kursorem — unikalny endpoint, nie ma go dla innych encji.

Przykład: utworzenie towaru z cenami

curl -X POST https://optima.twoja-firma.pl/api/towary/ \
  -H "X-Api-Key: <klucz>" \
  -H "X-Optima-Firma: FIRMA_GLOWNA" \
  -H "Content-Type: application/json" \
  -d '{
    "kod": "LAPTOP-LEN-X1",
    "nazwa": "Laptop Lenovo ThinkPad X1 Carbon",
    "nazwaFiskalna": "Laptop ThinkPad X1",
    "jm": "szt",
    "stawka": 23,
    "ean": "5901234567890",
    "grupa": "KOMPUTERY/LAPTOPY",
    "typ": 0,
    "produkt": 0,
    "numerKat": "20Y400ABPB",
    "wagaKG": 1.13,
    "url": "https://sklep.firma.pl/laptop-lenovo-thinkpad-x1",
    "opis": "Laptop biznesowy 14 cali, Intel Core i7, 16GB RAM, 512GB SSD",
    "ceny": [
      { "numer": 1, "wartoscNetto": 5900.00, "waluta": "PLN" },
      { "numer": 2, "wartoscNetto": 5500.00, "waluta": "PLN" }
    ]
  }'

Odpowiedź HTTP 201 zawiera pełny TowarDto — Optima nadaje id, weryfikuje stawkę VAT, waliduje unikalność kodu, przelicza ceny brutto na podstawie stawki.

Przykład: incremental sync

Twój system sklepu internetowego synchronizuje katalog co 15 minut. Pierwsze wywołanie pobiera wszystko, kolejne — tylko zmienione:

# Pierwsza synchronizacja (od początku)
curl "https://optima.../api/sync/towary?since=1970-01-01T00:00:00Z&limit=500" \
  -H "X-Api-Key: <klucz>" -H "X-Optima-Firma: FIRMA"

# Zwraca: { items: [...500...], cursor: "eyJpZCI6MTIzNDV9", hasMore: true }

# Kolejna strona
curl "https://optima.../api/sync/towary?cursor=eyJpZCI6MTIzNDV9"

# ... aż hasMore: false

# Potem — tylko zmiany od ostatniego sync
curl "https://optima.../api/sync/towary?since=2026-04-18T10:00:00Z"

Kursor zwraca się w odpowiedzi; używasz go zamiast offset dla kolejnej strony. To gwarantuje spójność nawet jeśli towary zmieniają się podczas iteracji (klasyczny problem z offset-based pagination w systemach live).

Przykład: stany magazynowe batch dla synchronizacji

Sklep internetowy ma 2000 aktywnych SKU i potrzebuje aktualizować dostępność co kilka minut:

curl -X POST https://optima.../api/towary/stany/batch/read \
  -H "Content-Type: application/json" \
  -d '{"kody": ["LAPTOP-LEN-X1", "LAPTOP-LEN-X2", ...] }'

Odpowiedź (max 500 pozycji na request, więc 2000 = 4 requesty):

{
  "items": [
    {
      "kod": "LAPTOP-LEN-X1",
      "stany": [
        {
          "magazynId": 1,
          "magazynSymbol": "GŁÓWNY",
          "magazynNazwa": "Magazyn główny",
          "czyAgregat": false,
          "ilosc": 15,
          "rezerwacje": 3,
          "braki": 0,
          "dostepne": 12,
          "zamowienia": 50
        },
        {
          "magazynId": null,
          "czyAgregat": true,
          "ilosc": 25,
          "dostepne": 22
        }
      ]
    }
  ]
}

Do sklepu wysyłasz dostepne (12 szt na magazyn główny, 22 szt agregat wszystkich magazynów), nie ilosc — inaczej pokażesz klientom produkty, których nie możesz zrealizować (bo są już zarezerwowane pod inne zamówienia).

Przykład: ceny per kontrahent (B2B)

Kluczowy klient ma negocjowaną cenę na laptopa — niżej niż standardowy cennik:

curl -X PUT https://optima.../api/towary/LAPTOP-LEN-X1/atrybuty/CENA_SPEC/kontrahenci \
  -H "Content-Type: application/json" \
  -d '{"kontrahentKod": "BIGCORP", "wartosc": "5200.00"}'

Warunek: atrybut CENA_SPEC musi być w Optimie zdefiniowany jako zależny od kontrahenta (Ogólne → Definicje atrybutów → flaga „zależny od kontrahenta”). Inaczej PUT zwróci 400.

Przy wystawianiu faktury dla BIGCORP Optima podstawia ten atrybut (jeśli Twoja integracja go używa przy wyliczaniu ceny). Standard to raczej cenniki (Cenniki API), ale atrybuty dają elastyczność dla specjalnych przypadków poza cennikiem.

Wydajność

  • Pobieranie pojedyncze (GET): < 100 ms typowo
  • Batch read 500 towarów: ~500 ms — 2 s (SQL-backed)
  • Create pojedynczo: ~0,5 — 2 s (COM Save + walidacja)
  • Create batch 100: ~5 — 20 s (jedna sesja COM, jeden Save)
  • Sync z kursorem 500 rekordów: < 1 s (SQL przyrostowy)

Dla katalogów powyżej 10k SKU praktycznym wzorcem jest:

  1. Initial full sync/api/sync/towary + paginacja kursorem, ~2-5 min dla 50k SKU
  2. Incremental sync — co 5-15 minut, pobiera 10-500 rekordów
  3. Push-based stany/api/towary/stany/batch/read co 1-5 minut dla aktywnych SKU, wypychanie do sklepu

Versync automatyzuje ten wzorzec out-of-the-box (konfigurujesz raz, działa w tle z retry, monitoring, alertami przy dryfie).

Kiedy towar, kiedy usługa

W CreateTowarRequest pole typ decyduje o zachowaniu:

  • 0 (towar) — wpływa na stany magazynowe, wymaga magazynu, generuje WZ/PZ przy sprzedaży/zakupie
  • 1 (usługa) — bez stanów, bez magazynu; faktura nie generuje dokumentu wydania
  • 2 (produkt) — towar złożony z komponentów (receptura). Wymaga produkt=1 i zdefiniowanych składników osobno

Wybór typu ma konsekwencje księgowe (schematy dekretacji) i nie jest zmienialny po utworzeniu. Dla e-commerce sprzedającego fizyczne produkty = zawsze 0. Dla SaaS/usług fakturowanych = 1. Dla firm produkcyjnych z własną recepturą = 2.

Kiedy się tego używa w praktyce

Typowe sytuacje, w których integracja przez REST API oszczędza dni pracy programistycznej.

Synchronizacja katalogu produktów do sklepu

GET /api/sync/towary?since=<timestamp> z kursorem — pobierasz tylko zmienione towary od ostatniego sync. Aktualizujesz pola, ceny, opisy w sklepie. Bezpieczne dla dużych katalogów (50k+ produktów) — nie pobierasz wszystkiego za każdym razem.

Real-time sync stanów magazynowych

Zamówienie w sklepie zmniejsza stan w Optimie (przez FS + WZ). Versync polluje /api/towary/stany/batch/read co 30s dla aktywnych SKU, porównuje i wypycha diff do sklepu. Alternatywa: webhook z Optimy (custom) po zmianie stanu.

Masowy import katalogu z Excela / PIM

POST /api/towary/batch z 100 towarami za jednym razem. Dla 10k produktów — 100 requestów × ~3-5 sek każdy = ~10 minut. Bez batchowania — pojedynczo ~4x wolniej.

Wyszukiwanie towaru przy obsłudze B2B

Sprzedawca w panelu B2B wpisuje fragment nazwy lub kodu — GET /api/towary/wyszukaj?nazwa=laptop&kod=LP. Szybki autocomplete. Limit wyników (max) chroni przed zalewem, flaga truncated informuje że jest więcej.

Cennik per kontrahent przez atrybuty zależne

Kontrahent-kluczowy ma specjalną cenę na produkt X. PUT /api/towary/X/atrybuty/CENA_SPECJALNA/kontrahenci z payloadem {kontrahentKod, wartosc: 89.00}. Dla innych klientów cena standardowa. Unikalne w Optimie — rzadkie w konkurencyjnych ERP.

Pomocnicze jednostki miary dla opakowań hurtowych

Towar jest sprzedawany w szt lub opakowaniach (12 szt). POST /api/towary/{kod}/jednostki-miary z {jm: 'opak', przelicznik: 12}. Sprzedawca wystawiający fakturę wybiera jednostkę, Optima liczy cenę i stan automatycznie.

Czego nie ma w oficjalnej dokumentacji Comarch

Wiedza z realnych wdrożeń — rzeczy, na których inaczej się przewrócisz.

Typ nie do zmiany po utworzeniu

Typ (towar/usługa/produkt) jest ustawiany przy tworzeniu i Optima nie pozwala go zmienić. Jeśli pomyliłeś typ — musisz usunąć i utworzyć na nowo (o ile nie ma dokumentów historycznych). W integracji dobierz typ z góry — dla e-commerce to zwykle 0 (towar), dla fakturowanych usług 1.

Stan magazynowy ≠ ilość dostępna

Stany mają kilka pól: Ilosc (fizycznie w magazynie), Rezerwacje (zarezerwowane do zamówień), Zamowienia (zamówione u dostawcy, w drodze), Braki (braki do wydania), Dostepne (Ilosc - Rezerwacje - Braki). Dla synchronizacji do sklepu użyj **Dostepne**, nie Ilosc — inaczej pokażesz stan, którego klient nie może kupić.

Nazwa fiskalna — inne zasady niż pełna nazwa

nazwaFiskalna to osobne pole z ograniczeniem 40 znaków i regulacjami dotyczącymi czytelności dla urzędu skarbowego (nie może być 'produkt X' — musi opisywać rzeczywistą treść). Jeśli integracja nie ustawi nazwaFiskalnej, Optima skraca nazwę — co może być niezgodne z przepisami.

Cena netto vs brutto — zależne od cennika

Każdy cennik w Optimie ma flagę typu ceny (netto lub brutto). CenaDto zwraca obie (wartoscNetto + wartoscBrutto), ale przy tworzeniu towaru podaj zgodnie z typem cennika. Cennik netto + cena brutto w requestie → Optima przeliczy, ale zaokrąglenia mogą się różnić o grosze.

Sync endpoint używa kursora, nie offsetu

GET /api/sync/towary zwraca stronę + cursor do następnej. Przy dużych katalogach nie używaj offset (strona 1, strona 2...) — tylko przekazuj cursor z poprzedniej odpowiedzi. Gwarantuje spójność nawet gdy dane zmienią się podczas iteracji.

Atrybuty towar-kontrahent vs atrybuty standardowe

Zwykły atrybut ma jedną wartość per towar. Atrybut zależny od kontrahenta ma osobną wartość dla każdej pary (towar, kontrahent). Używa się innego endpointa (/atrybuty/{kod}/kontrahenci). W Optimie atrybut musi być zdefiniowany jako 'zależny od kontrahenta' — inaczej PUT zwróci 400.

Jednostka miary bazowa ustawiona przy create

jm w CreateTowarRequest to jednostka bazowa. Dodatkowe jednostki dodaje się osobnym endpointem (/jednostki-miary). Zmienić jednostki bazowej nie można po utworzeniu jeśli są dokumenty historyczne — Optima odmówi.

Batch 100 dla write, 500 dla read

POST /batch (create) i PUT /batch/update mają max 100. POST /batch/read ma max 500. Powód: zapis wymaga walidacji biznesowej (wolne), odczyt może iść szybszą ścieżką (szybkie).

Nieaktywny ≠ usunięty

Ustawienie nieaktywny=true ukrywa towar z list wyboru, ale zachowuje całą historię sprzedaży, zakupów, stanów. DELETE próbuje fizycznie usunąć — powiedzie się tylko jeśli brak dokumentów powiązanych. Dla e-commerce typowo używa się nieaktywny, nie DELETE.

EAN — tylko jeden pod kluczem, pozostałe przez atrybuty

Pole ean mieści jeden kod. Jeśli towar ma wiele kodów (różne opakowania, wariant), dodatkowe EAN-y zapisuje się jako atrybuty (np. EAN_OPAK, EAN_KARTON). Przy integracji ze sklepem / marketplace musisz obsłużyć wszystkie.

Częste pytania

Jaka jest różnica między /api/towary/batch/read a /api/towary/wyszukaj?

batch/read pobiera konkretne towary, które znasz po kodzie (max 500, bez filtrowania). wyszukaj szuka po fragmencie tekstu (nazwa/kod/numerKat/ean), zwraca krótką listę z flagą truncated — dla interaktywnego autocomplete, nie dla masowego pobierania.

Czy sync endpoint zwróci usunięte towary?

Typowo nie — sync operuje na istniejących rekordach. Jeśli towar został usunięty z Optimy (DELETE fizyczny), nie pojawi się w sync. Dla detekcji usunięć stosuje się atrybut 'nieaktywny' + periodic full-scan raz na dobę.

Jak odpowiednio zsynchronizować stany magazynowe do 3 sklepów naraz?

Pobierasz raz /api/towary/stany/batch/read z listą SKU, potem wypychasz do 3 sklepów. Nie rób osobnego pobierania per sklep — to 3× obciążenie Optimy. Versync robi to automatycznie (fan-out: 1 pobranie → N celów).

Czy cena w cenniku 'detaliczna' może być różna w PLN i EUR?

Tak — każda pozycja CenaDto ma pole waluta. Cennik może mieć wiele walut (multi-currency pricelist). Przy tworzeniu towaru wyślij tablicę cen dla każdej waluty osobno.

Ile trwa sync 50 tys. towarów?

Initial sync (wszystko od zera): pobieranie po 500 na stronę = 100 requestów × ~1 sek = ~2 minuty dla samych danych podstawowych. Incremental sync (tylko zmienione od czasu X) zwykle 100-500 rekordów dziennie — sekundy.

Gdzie w Optimie widzę to, co zmienia moja integracja?

Handel → Towary → karta towaru. Zmiany stanów: Magazyn → Stany. Ceny: Ogólne → Cenniki. Atrybuty: na karcie towaru → zakładka 'Atrybuty'. Każda edycja przez API jest widoczna dla operatora identycznie jak ręczne zmiany w oknie.

Potrzebujesz integracji z Optimą?

Uruchomimy Optima API u Ciebie lub u Twojego klienta. Licencja jednorazowa, roczny serwis kompatybilności, pełny dostęp do Swagger i wsparcie wdrożeniowe.