Authorization header.| Field | Type | Description |
|---|---|---|
| username | required | Your La Grana system login |
| password | required | Your password |
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"}'
{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
The access token is valid for 60 minutes. Refresh via POST /api/token/refresh/ with the refresh token.
Attach token to every request:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
next field to iterate through pages.https://b2b.lagrana.pl/api/
{
"count": 1248, // total number of results
"next": "https://b2b.lagrana.pl/api/products/?page=2",
"previous": null,
"results": [ /* ... */ ]
}
client_price is the final price after discount; client_price_without_discount is the base price before discount.| Parameter | Type | Description |
|---|---|---|
| page | optional | Page number (default 1) |
| brand | optional | Filter by brand name |
| categories | optional | Filter by category ID |
| status | optional | Filter by status ID |
| active | optional | true / false |
| search | optional | Search by SKU or status |
Response fields
■ price fields ■ nested objects/arrays
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"
{
"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", // price after discount
"client_price_without_discount": "99.00", // base price
"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...",
// also: 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" }
]
}
]
}
Returns a single product object with the same fields as the list. Use the numeric internal ID from the id field, or use the products-sku endpoint to look up by 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"
Returns the same fields as GET /api/products/{id}/. Returns 404 if the SKU does not exist.
Same filtering parameters as /api/products/ (brand, categories, status, active, search). The detail endpoint uses SKU as the key instead of numeric ID.
parent field to build a category tree.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"
[
{
"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 of parent category, null = top level
}
]
/api/products/. Default page size is 1 000 — for full catalog sync use ?all=1.| Parameter | Type | Description |
|---|---|---|
| all | optional | Set to 1 to disable pagination and return all products in a single response. Recommended for full catalog sync. |
| page_size | optional | Number of results per page (default: 1000, max: 10000). Ignored when all=1. |
| brand | optional | Filter by brand |
| categories | optional | Filter by category ID |
| search | optional | Search by 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"
GET /api/stock/
GET /api/stock/?page=2
GET /api/stock/?page_size=500
{
"count": 4432, // total products
"next": "https://.../api/stock/?page=2",
"previous": null,
"results": [
{ "sku": "LG-12345", "stock_warehouse": 24, "stock_supplier": 10 },
...
]
}
?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 },
...
] // flat array, no count/next/previous wrapper
stock_warehouse — quantity in La Grana warehouse
stock_supplier — quantity available at supplier
multipart/form-data (not JSON).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"
{
"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"
}
]
}
]
}
Delivery address (required)
| Field | Type | Description |
|---|---|---|
| first_name | required | Recipient's first name |
| last_name | required | Recipient's last name |
| address | required | Street and number |
| postal | required | Postal code |
| city | required | City |
| shipping_label | optional | Shipping label file (PDF/ZPL) |
| country_code | optional | 2-letter country code (default: PL) |
| phone | optional | Recipient's phone |
| delivery_email | optional | Recipient's e-mail |
Order fields
| Field | Type | Description |
|---|---|---|
| items | required | JSON string — array of items (see below) |
| dropshipping | optional | Dropshipping order — true / false (default: false). If false, a TRANSPORT-PL line is added in Vendo; if true, a DROPSHIPPING line is added. |
| external_id | optional | Your own reference number |
| notes | optional | Order notes |
Each item in the items JSON array:
| Field | Type | Description |
|---|---|---|
| product | product or sku | Internal product ID |
| sku | product or sku | Product SKU (alternative to product) |
| qty | required | Quantity (min. 1) |
Prices, VAT rates and line totals are calculated server-side from the client's assigned price list — do not send price data.
Validation — what returns 400
{
"non_field_errors": [
"Product 'LG-99999' does not exist.",
"Insufficient stock for 'LG-12345': ordered 10, available 3."
]
}
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"
<?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> <!-- your price --> <stock>24</stock> <image>https://cdn.lagrana.pl/img/lg-12345-1.jpg</image> <active>true</active> </product> </products>
The feed requires no authentication header — the token in the URL grants access. Prices reflect your individual rates.
detail field with a description.| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created (e.g. new order) |
| 400 | Request error — check parameters or required fields |
| 401 | Missing authentication or expired token |
| 403 | No permission to access resource |
| 404 | Resource not found |
| 429 | Request limit exceeded (rate limiting) |
Log in and start using the API
API access is granted on request — contact us or log in if you already have an account.