# API dla partnerów

Ceny, stany, zamówienia, proformy, faktury, wysyłki i numery seryjne dla Twojego sklepu lub systemu. Działa na koncie B2B, z tymi samymi cenami co na stronie.

Adres: `https://b2b.z-ecoenergy.com/api/v1`. Tylko HTTPS. Zapytanie przez HTTP dostaje błąd `https_required` i nie jest przekierowywane.

## Logowanie

```
POST /api/v1/login
Content-Type: application/json

{"email": "biuro@twojafirma.pl", "password": "…", "accept_price_confidentiality": true}
```

```
{
  "token": "zk1.…",
  "expires_at": "2026-09-27T15:20:00.000Z",
  "feed": {"csv": "https://b2b.z-ecoenergy.com/api/v1/feed/zf1.….csv", "xml": "…"}
}
```

`accept_price_confidentiality: true` to ta sama zgoda co przy logowaniu na stronie: ceny i rabaty są poufne.

Token jest ważny 2 godziny. Wysyłaj go w nagłówku każdego zapytania:

```
Authorization: Bearer zk1.…
```

Po wygaśnięciu dostaniesz `401`. Zaloguj się wtedy ponownie. Token i hasło nigdy nie idą w adresie URL.

## Produkty

Produkt to jego link na stronie (`url`).

```
GET /api/v1/products
GET /api/v1/products?url=/product/produkt-1&url=/product/produkt-2
GET /api/v1/products?currency=EUR
```

Bez parametrów zwraca cały katalog. `url` przyjmuje pełny link albo samą ścieżkę. `currency`: `PLN` (domyślnie), `EUR` lub `USD`.

```
{
  "currency": "PLN",
  "products": [
    {"url": "https://b2b.z-ecoenergy.com/product/produkt-1", "name": "Przykładowy produkt",
     "stock": 12, "price_net": 100.00, "price_gross": 123.00},
    {"url": "https://b2b.z-ecoenergy.com/product/produkt-2", "name": "Przykładowy moduł",
     "stock": 900, "price_net": 300.00, "price_gross": 369.00, "pack": 36,
     "tiers": [{"min_quantity": 180, "price_net": 290.00, "price_gross": 356.70}]}
  ]
}
```

- `price_net`, `price_gross`: Twoja cena za sztukę. `null` to cena na zapytanie. W EUR i USD tylko netto.
- `pack`: tylko gdy produkt sprzedajemy w opakowaniach. Zamawiaj wielokrotności.
- `tiers`: tylko gdy cena spada od danej ilości.

Odpowiedź ma nagłówek `ETag`. Wyślij go z powrotem w `If-None-Match`, a jeśli nic się nie zmieniło, dostaniesz pustą odpowiedź `304`.

## Zamówienie

```
POST /api/v1/orders
Authorization: Bearer zk1.…
Content-Type: application/json

{
  "items": [
    {"url": "https://b2b.z-ecoenergy.com/product/produkt-1", "quantity": 2},
    {"url": "https://b2b.z-ecoenergy.com/product/produkt-2", "quantity": 36}
  ],
  "currency": "PLN",
  "payment": "transfer",
  "delivery": {
    "company": "Firma Klienta", "first_name": "Jan", "last_name": "Kowalski",
    "street": "Polna 1", "postcode": "95-030", "city": "Rzgów", "country": "PL",
    "phone": "600100200"
  },
  "reference": "10234",
  "notes": "Proszę o kontakt przed dostawą",
  "accept_terms": true
}
```

```
201 {"status": "accepted", "reference": "10234"}
```

| Pole | Opis |
|---|---|
| `items` | `url` produktu i `quantity` (liczba całkowita; przy produktach z `pack` wielokrotność `pack`) |
| `currency` | Waluta proformy: `PLN`, `EUR` lub `USD` |
| `payment` | `transfer` (przelew), `cash_on_delivery` (za pobraniem, tylko z dostawą) lub `cash` (gotówka, tylko odbiór osobisty) |
| `pickup` | `true` dla odbioru osobistego w Rzgowie. Wtedy bez `delivery`. |
| `delivery` | Adres dostawy, na przykład Twojego klienta. Wymagane `first_name`, `postcode`, `city`. `country` to kod kraju, np. `DE`. |
| `reference` | Twój numer zamówienia: litery, cyfry, `.` `_` `/` `-`, do 40 znaków |
| `notes` | Uwagi, do 300 znaków |
| `accept_terms` | `true`: akceptacja [regulaminu](/cdn/_files/Regulamin.pdf) |

Zamówienie staje się proformą. Stany sprawdzamy przy każdym zamówieniu. Brak na stanie lub ilość spoza wielokrotności pakowania to błąd, nigdy cicha zmiana ilości.

Kolejne zamówienie z tego samego konta można złożyć po 2 minutach. Wcześniej dostaniesz `429 order_too_soon`.

Numeru proformy odpowiedź nie zawiera. Znajdziesz ją przez `GET /api/v1/proformas?reference=10234`. Jeśli połączenie zerwie się przed odpowiedzią, sprawdź to, zanim wyślesz zamówienie jeszcze raz.

## Proformy

```
GET /api/v1/proformas
GET /api/v1/proformas?reference=10234
```

```
{"proformas": [{
  "number": "OF/FPF/26/000001", "date": "2026-09-27", "currency": "PLN",
  "net": 200.00, "gross": 246.00, "status": "Nie weryfikowana", "reference": "10234",
  "pdf_url": "https://b2b.z-ecoenergy.com/api/v1/proformas/OF-FPF-26-000001/pdf"
}]}
```

PDF pobierzesz spod `pdf_url`, z tym samym nagłówkiem `Authorization`. `null` znaczy, że PDF tej proformy nie jest dostępny przez API.

## Faktury i wysyłki

```
GET /api/v1/invoices
GET /api/v1/shipments
```

Faktury: `number`, `date`, `currency`, `net`, `gross`, `pdf_url`. Wysyłki:

```
{"shipments": [{
  "number": "WZ/RZ /26/000001", "date": "2026-09-28", "status": "Wysłane",
  "carrier": "DPD", "tracking_number": "0000000000000",
  "pdf_url": "https://b2b.z-ecoenergy.com/api/v1/shipments/WZ-RZ-26-000001/pdf"
}]}
```

`carrier` i `tracking_number` są `null`, dopóki paczka nie wyjdzie z magazynu.

## Numery seryjne

```
GET /api/v1/serial-numbers
```

```
{"documents": [{
  "number": "WZ/1  /26/000001", "date": "2026-09-20",
  "url": "https://b2b.z-ecoenergy.com/api/v1/serial-numbers/WZ-1-26-000001",
  "pdf_url": "https://b2b.z-ecoenergy.com/api/v1/serial-numbers/WZ-1-26-000001/pdf"
}]}
```

`url` zwraca numery z dokumentu:

```
{"number": "WZ/1  /26/000001", "items": [{"name": "Przykładowy produkt", "serial": "SN0000001"}]}
```

## Błędy

Każdy błąd to `{"error": "kod"}`, czasem z dodatkowymi polami.

| HTTP | `error` | Co zrobić |
|---|---|---|
| 400 | `https_required` | Użyj HTTPS |
| 400 | `credentials_required`, `confidentiality_required`, `invalid_json`, `currency_invalid` | Popraw zapytanie |
| 400 | `items_required`, `duplicate_item`, `quantity_invalid` (z `urls`), `quantity_not_pack_multiple` (z `items`), `terms_required`, `payment_invalid`, `payment_not_allowed`, `delivery_required`, `postcode_invalid`, `phone_invalid`, `country_invalid`, `reference_invalid` | Popraw zamówienie |
| 401 | `wrong_credentials`, `unauthorized` | Zaloguj się ponownie |
| 404 | `not_found`, `pdf_not_available` | Nie ma takiego zasobu na Twoim koncie |
| 405 | `method_not_allowed` | Zła metoda HTTP |
| 409 | `unknown_items` (z `urls`), `unavailable_items`, `stock_exceeded` (z `items`: `url`, `requested`, `available`) | Zmień pozycje |
| 413, 415 | `body_too_large`, `json_required` | Treść do 1 MB, typ `application/json` |
| 422 | `order_rejected` | Zamówienie odrzucone, skontaktuj się z nami |
| 429 | `order_too_soon`, `rate_limited` | Odczekaj (nagłówek `Retry-After`) |
| 502, 503 | `order_failed`, `unavailable`, `pdf_unavailable` | Spróbuj ponownie za chwilę |

## Plik produktów (CSV, XML)

Dla sklepów, które importują ofertę z pliku. Link do pliku CSV lub XML dostajesz w odpowiedzi na logowanie (pole `feed`). Działa do zmiany hasła. Kto go ma, widzi Twoje ceny, więc nie wklejaj go w miejsca publiczne.

Plik ma kolumny `url`, `name`, `stock`, `price_net`, `price_gross`, jak w `products`. Opcje w adresie: `?currency=EUR` oraz `?separator=semicolon` (CSV ze średnikiem).

Jeśli importer ma pola login i hasło, użyj zamiast linku `https://b2b.z-ecoenergy.com/api/v1/feed.csv` lub `feed.xml` z e-mailem i hasłem konta (HTTP Basic).

Pytania: [kontakt](/contact).
