REST API v1

Integracja przez REST API

Pobieraj produkty, stany magazynowe i swoje indywidualne ceny La Grana bezpośrednio do swojego systemu. Pełna automatyzacja z uwierzytelnianiem JWT.

🔐
JWT Auth
Bezpieczne uwierzytelnianie tokenami JWT z automatycznym odświeżaniem.
REST + JSON
Standardowe endpointy REST z odpowiedziami JSON. Prosto i przewidywalnie.
💰
Indywidualne ceny
Każde konto ma przypisane indywidualne ceny i rabat — widoczne bezpośrednio w odpowiedziach API.
Uwierzytelnianie
API La Grana używa JWT (JSON Web Tokens). Najpierw uzyskaj token, a następnie dołącz go do każdego żądania w nagłówku Authorization.
POST /api/token/ Uzyskaj token dostępu
PoleTypOpis
usernamewymaganeTwój login do systemu La Grana
passwordwymaganeTwoje hasło
import requests

resp = requests.post("https://b2b.lagrana.pl/api/token/",
    json={"username": "twoj_login", "password": "haslo"})
token = resp.json()["access"]
headers = {"Authorization": f"Bearer {token}"}
$res = file_get_contents('https://b2b.lagrana.pl/api/token/', false,
    stream_context_create(['http' => [
        'method'  => 'POST',
        'header'  => 'Content-Type: application/json',
        'content' => json_encode(['username'=>'twoj_login', 'password'=>'haslo'])
    ]])
);
$token = json_decode($res)->access;
$authHeader = "Authorization: Bearer $token";
curl -X POST https://b2b.lagrana.pl/api/token/ \
  -H "Content-Type: application/json" \
  -d '{"username": "twoj_login", "password": "haslo"}'
Odpowiedź
{
  "access":  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Token access ważny przez 60 minut. Odśwież przez POST /api/token/refresh/ podając token refresh.

Dołącz token do każdego żądania:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Base URL i format
Wszystkie żądania kieruj na poniższy adres. API zwraca i przyjmuje dane w formacie JSON (UTF-8). Wszystkie endpointy listowania są paginowane — używaj pola next do iterowania stron.
https://b2b.lagrana.pl/api/
Odpowiedź z paginacją
{
  "count": 1248,        // łączna liczba wyników
  "next":  "https://b2b.lagrana.pl/api/products/?page=2",
  "previous": null,
  "results": [ /* ... */ ]
}
Produkty
Pełny katalog produktów z opisami, zdjęciami, atrybutami i Twoimi indywidualnymi cenami. client_price to cena końcowa po rabacie; client_price_without_discount to cena bazowa przed rabatem.
GET /api/products/ Lista produktów (paginacja)
ParametrTypOpis
pageopcjonalnyNumer strony (domyślnie 1)
brandopcjonalnyFiltruj po nazwie marki
categoriesopcjonalnyFiltruj po ID kategorii
statusopcjonalnyFiltruj po ID statusu
activeopcjonalnytrue / false
searchopcjonalnySzukaj po SKU lub statusie

Pola odpowiedzi

id sku name brand ean active vat_rate for_who advantages stock_warehouse stock_supplier added_at updated_at client_price client_price_without_discount categories [ ] status { } prod_image [ ] description { } additional [ ]

pola cenowe   zagnieżdżone obiekty/tablice

url = "https://b2b.lagrana.pl/api/products/"
while url:
    data = requests.get(url, headers=headers).json()
    for p in data["results"]:
        print(p["sku"], p["client_price"], p["stock_warehouse"])
    url = data["next"]
$url = 'https://b2b.lagrana.pl/api/products/';
while ($url) {
    $data = json_decode(file_get_contents($url, false,
        stream_context_create(['http' => ['header' => $authHeader]])
    ));
    foreach ($data->results as $p) {
        echo $p->sku . ' ' . $p->client_price . "\n";
    }
    $url = $data->next;
}
curl "https://b2b.lagrana.pl/api/products/" \
  -H "Authorization: Bearer $TOKEN"

# kolejna strona
curl "https://b2b.lagrana.pl/api/products/?page=2" \
  -H "Authorization: Bearer $TOKEN"
Odpowiedź
{
  "count": 1248,
  "next": "https://b2b.lagrana.pl/api/products/?page=2",
  "previous": null,
  "results": [
    {
      "id": 42,
      "sku": "LG-12345",
      "name": "Nazwa produktu",
      "brand": "BrandName",
      "ean": "1234567890123",
      "active": true,
      "vat_rate": 23,
      "for_who": "Dla niej",
      "advantages": ["Zaletą jest...", "..."],
      "stock_warehouse": 24,
      "stock_supplier": 10,
      "added_at": "2025-01-15T08:00:00Z",
      "updated_at": "2026-03-01T12:00:00Z",
      "client_price": "84.15",               // cena po rabacie
      "client_price_without_discount": "99.00", // cena bazowa
      "categories": [
        { "id": 3, "name_pl": "Wibratory", "name_en": "Vibrators", "parent": null }
      ],
      "status": { "id": 1, "name": "Nowość" },
      "prod_image": [
        { "image": "https://cdn.lagrana.pl/img/lg-12345-1.jpg", "main_image": true, "hoover_image": false },
        { "image": "https://cdn.lagrana.pl/img/lg-12345-2.jpg", "main_image": false, "hoover_image": true }
      ],
      "description": {
        "id": 5, "product": 42,
        "lang_pl": "Opis po polsku...",
        "lang_en": "Description in English...",
        "lang_de": "Beschreibung auf Deutsch...",
        // oraz: lang_nl, lang_fr, lang_es, lang_it, lang_gr, lang_cz, lang_sk, lang_ua, lang_ru
      },
      "additional": [
        { "id": 101, "product": 42, "item": "Kolor", "value": "Czarny" },
        { "id": 102, "product": 42, "item": "Długość", "value": "18 cm" }
      ]
    }
  ]
}
GET /api/products/{id}/ Pojedynczy produkt po ID

Zwraca jeden obiekt produktu z tymi samymi polami co lista. Użyj numerycznego ID z pola id, lub użyj endpointu products-sku do wyszukiwania po SKU.

Produkt po SKU
Pobierz pojedynczy produkt bezpośrednio po kodzie SKU — przydatne przy synchronizacji z zewnętrznymi systemami, gdzie wewnętrzne ID nie jest znane.
GET /api/products-sku/{sku}/ Produkt po kodzie SKU
p = requests.get(
    "https://b2b.lagrana.pl/api/products-sku/LG-12345/",
    headers=headers
).json()
print(p["name"], p["client_price"], p["client_price_without_discount"])
$p = json_decode(file_get_contents(
    'https://b2b.lagrana.pl/api/products-sku/LG-12345/', false,
    stream_context_create(['http' => ['header' => $authHeader]])
));
echo $p->name . ' ' . $p->client_price;
curl "https://b2b.lagrana.pl/api/products-sku/LG-12345/" \
  -H "Authorization: Bearer $TOKEN"

Zwraca te same pola co GET /api/products/{id}/. Zwraca 404 jeśli SKU nie istnieje.

GET /api/products-sku/ Lista z filtrowaniem

Te same parametry filtrowania co /api/products/ (brand, categories, status, active, search). Endpoint szczegółowy używa SKU jako klucza zamiast numerycznego ID.

Kategorie
Pełna lista kategorii z wielojęzycznymi nazwami. Użyj pola parent do budowania drzewa kategorii.
GET /api/categories/ Lista kategorii
cats = requests.get("https://b2b.lagrana.pl/api/categories/",
    headers=headers).json()
for c in cats:
    print(c["id"], c["name_pl"])
$cats = json_decode(file_get_contents(
    'https://b2b.lagrana.pl/api/categories/', false,
    stream_context_create(['http' => ['header' => $authHeader]])
));
foreach ($cats as $c) echo $c->id . ' ' . $c->name_pl . "\n";
curl "https://b2b.lagrana.pl/api/categories/" \
  -H "Authorization: Bearer $TOKEN"
Odpowiedź
[
  {
    "id": 1,
    "name_pl": "Wibratory",
    "name_en": "Vibrators",
    "name_de": "Vibratoren",
    "name_nl": null,
    "name_fr": null,
    "name_es": null,
    "name_it": null,
    "name_gr": null,
    "name_cz": null,
    "name_sk": null,
    "name_ua": null,
    "name_ru": null,
    "parent": null   // ID kategorii nadrzędnej, null = najwyższy poziom
  }
]
Stany magazynowe
Lekki endpoint do sprawdzania dostępności bez pobierania pełnych danych produktu. Obsługuje te same filtry co /api/products/. Domyślny rozmiar strony to 1 000 — przy synchronizacji całego katalogu użyj ?all=1.
GET /api/stock/ Stany aktywnych produktów
ParametrTypOpis
allopcjonalnyUstaw na 1 aby wyłączyć paginację i pobrać wszystkie produkty w jednym zapytaniu. Zalecane przy pełnej synchronizacji katalogu.
page_sizeopcjonalnyLiczba wyników na stronę (domyślnie: 1000, maks: 10000). Ignorowany przy all=1.
brandopcjonalnyFiltruj po marce
categoriesopcjonalnyFiltruj po ID kategorii
searchopcjonalnySzukaj po SKU
# wszystkie stany w jednym zapytaniu
stock = requests.get("https://b2b.lagrana.pl/api/stock/?all=1",
    headers=headers).json()
for item in stock:
    print(item["sku"], item["stock_warehouse"], item["stock_supplier"])
$stock = json_decode(file_get_contents(
    'https://b2b.lagrana.pl/api/stock/?all=1', false,
    stream_context_create(['http' => ['header' => $authHeader]])
));
foreach ($stock as $item) {
    echo $item->sku . ' ' . $item->stock_warehouse . "\n";
}
# wszystkie stany naraz
curl "https://b2b.lagrana.pl/api/stock/?all=1" \
  -H "Authorization: Bearer $TOKEN"
Z paginacją (domyślnie)
GET /api/stock/
GET /api/stock/?page=2
GET /api/stock/?page_size=500

{
  "count":    4432,   // łączna liczba produktów
  "next":     "https://.../api/stock/?page=2",
  "previous": null,
  "results": [
    { "sku": "LG-12345", "stock_warehouse": 24, "stock_supplier": 10 },
    ...
  ]
}
Wszystkie produkty naraz (?all=1)
GET /api/stock/?all=1

[
  { "sku": "LG-12345", "stock_warehouse": 24, "stock_supplier": 10 },
  { "sku": "LG-12346", "stock_warehouse": 0,  "stock_supplier": 50 },
  ...
]  // płaska tablica, bez wrappera count/next/previous

stock_warehouse — ilość w magazynie La Grana
stock_supplier — ilość dostępna u dostawcy

Zamówienia
Twórz i listuj swoje zamówienia. Każde konto widzi tylko swoje zamówienia. Produkty możesz podawać po wewnętrznym ID lub SKU. Tworzenie zamówienia wymaga adresu dostawy i pliku listu przewozowego — użyj multipart/form-data (nie JSON).
GET /api/orders/ Lista Twoich zamówień
orders = requests.get("https://b2b.lagrana.pl/api/orders/",
    headers=headers).json()
for o in orders["results"]:
    print(o["id"], o["status"], o["total_gross"])
$orders = json_decode(file_get_contents(
    'https://b2b.lagrana.pl/api/orders/', false,
    stream_context_create(['http' => ['header' => $authHeader]])
));
foreach ($orders->results as $o) {
    echo $o->id . ' ' . $o->status . ' ' . $o->total_gross . "\n";
}
curl "https://b2b.lagrana.pl/api/orders/" \
  -H "Authorization: Bearer $TOKEN"
Odpowiedź
{
  "count": 3,
  "results": [
    {
      "id": 7,
      "external_id": "WC-1234",
      "status": "new",        // new | confirmed | rejected
      "total_gross": "267.00",
      "notes": "Uwagi do zamówienia",
      "first_name": "Jan",
      "last_name": "Kowalski",
      "phone": "500100200",
      "delivery_email": "jan@sklep.pl",
      "address": "ul. Przykładowa 1",
      "postal": "00-001",
      "city": "Warszawa",
      "country_code": "PL",
      "shipping_label": "https://b2b.lagrana.pl/media/shipping_labels/etykieta.pdf",
      "dropshipping": false,
      "created_at": "2026-03-01T12:00:00Z",
      "updated_at": "2026-03-01T12:05:00Z",
      "items_read": [
        {
          "product": 42, "sku": "LG-12345", "name": "Nazwa produktu",
          "qty": 3, "unit_price_gross": "89.00",
          "vat_rate": 23, "line_total_gross": "267.00"
        }
      ]
    }
  ]
}
POST /api/orders/ Utwórz zamówienie
Content-Type: multipart/form-data

Adres dostawy (wymagane)

PoleTypOpis
first_namewymaganeImię odbiorcy
last_namewymaganeNazwisko odbiorcy
addresswymaganeUlica i numer
postalwymaganeKod pocztowy
citywymaganeMiasto
shipping_labelopcjonalny Plik listu przewozowego (PDF/ZPL)
country_codeopcjonalny2-literowy kod kraju (domyślnie: PL)
phoneopcjonalnyTelefon odbiorcy
delivery_emailopcjonalnyE-mail odbiorcy

Pola zamówienia

PoleTypOpis
itemswymaganeCiąg JSON — tablica pozycji (patrz niżej)
dropshippingopcjonalnyZamówienie dropshipping — true / false (domyślnie: false). Przy false do ZO trafia pozycja TRANSPORT-PL, przy true — DROPSHIPPING.
external_idopcjonalnyTwój numer referencyjny
notesopcjonalnyUwagi do zamówienia

Każda pozycja w tablicy JSON items:

PoleTypOpis
productproduct lub skuWewnętrzne ID produktu
skuproduct lub skuSKU produktu (alternatywa dla product)
qtywymaganeIlość (min. 1)

Ceny, stawki VAT i wartości pozycji są naliczane serwerowo z cennika przypisanego do konta — nie przesyłaj danych cenowych.

Walidacja — co zwraca 400

Produkt nie istnieje
Jeśli którekolwiek SKU lub ID produktu nie istnieje w katalogu, całe zamówienie jest odrzucane.
Niewystarczający stan magazynowy
Jeśli zamówiona ilość przekracza stan magazynowy którejkolwiek pozycji, zamówienie jest odrzucane. Wszystkie błędy są zwracane jednocześnie.
{
  "non_field_errors": [
    "Produkt o SKU 'LG-99999' nie istnieje.",
    "Niewystarczający stan dla 'LG-12345': zamówiono 10, dostępne 3."
  ]
}
Wszystko albo nic
Zamówienie trafia do bazy dopiero po przejściu walidacji wszystkich pozycji — nie powstają zamówienia częściowe.
import requests, json

with open("etykieta.pdf", "rb") as label_file:
    order = requests.post(
        "https://b2b.lagrana.pl/api/orders/",
        headers=headers,
        data={
            "first_name":   "Jan",
            "last_name":    "Kowalski",
            "address":      "ul. Przykładowa 1",
            "postal":       "00-001",
            "city":         "Warszawa",
            "external_id":  "WC-5678",
            "notes":        "Pilne",
            "items": json.dumps([
                {"sku": "LG-12345", "qty": 3},
                {"sku": "LG-12346", "qty": 1},
            ]),
        },
        files={"shipping_label": ("etykieta.pdf", label_file, "application/pdf")},
    )
print(order.json())
$ch = curl_init('https://b2b.lagrana.pl/api/orders/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ["Authorization: Bearer $token"],
    CURLOPT_POSTFIELDS     => [
        'first_name'     => 'Jan',
        'last_name'      => 'Kowalski',
        'address'        => 'ul. Przykładowa 1',
        'postal'         => '00-001',
        'city'           => 'Warszawa',
        'external_id'    => 'WC-5678',
        'notes'          => 'Pilne',
        'items'          => json_encode([
            ['sku' => 'LG-12345', 'qty' => 3],
            ['sku' => 'LG-12346', 'qty' => 1],
        ]),
        'shipping_label' => new CURLFile('/path/to/etykieta.pdf', 'application/pdf', 'etykieta.pdf'),
    ],
]);
var_dump(json_decode(curl_exec($ch)));
curl_close($ch);
curl -X POST "https://b2b.lagrana.pl/api/orders/" \
  -H "Authorization: Bearer $TOKEN" \
  -F "first_name=Jan" \
  -F "last_name=Kowalski" \
  -F "address=ul. Przykładowa 1" \
  -F "postal=00-001" \
  -F "city=Warszawa" \
  -F "external_id=WC-5678" \
  -F "notes=Pilne" \
  -F 'items=[{"sku":"LG-12345","qty":3},{"sku":"LG-12346","qty":1}]' \
  -F "shipping_label=@/path/to/etykieta.pdf"
Feed XML
Alternatywa dla API JSON — feed produktów w formacie XML, dostępny przez unikalny token (bez JWT). Przydatne dla platform obsługujących import XML (np. PrestaShop, IdoSell). Skontaktuj się z nami, aby otrzymać swój token.
GET /api/xml/{token}/ Feed XML produktów
XML
<?xml version="1.0" encoding="UTF-8"?>
<products>
  <product>
    <id>42</id>
    <sku>LG-12345</sku>
    <name>Nazwa produktu</name>
    <description>Opis po polsku...</description>
    <category>Wibratory</category>
    <brand>BrandName</brand>
    <price>84.15</price>   <!-- Twoja cena -->
    <stock>24</stock>
    <image>https://cdn.lagrana.pl/img/lg-12345-1.jpg</image>
    <active>true</active>
  </product>
</products>

Feed nie wymaga nagłówka uwierzytelniającego — token w URL stanowi dostęp. Ceny odzwierciedlają Twoje indywidualne stawki.

Kody błędów
API używa standardowych kodów HTTP. Odpowiedź błędu zawiera pole detail z opisem.
KodZnaczenie
200Sukces
201Zasób utworzony (np. nowe zamówienie)
400Błąd w żądaniu — sprawdź parametry lub wymagane pola
401Brak uwierzytelnienia lub wygasły token
403Brak uprawnień do zasobu
404Zasób nie istnieje
429Przekroczono limit zapytań (rate limiting)
Gotowy na integrację?

Zaloguj się i zacznij korzystać z API

Dostęp do API przyznajemy na wniosek — skontaktuj się z nami lub zaloguj, jeśli masz już konto.

Zaloguj się Zapytaj o dostęp