Partner API · Zamówienia

Instrukcja składania zamówień przez API

Wszystko, czego potrzebuje Twój zespół IT i zakupów, żeby wysyłać do Akces gotowe zamówienia: zasady współpracy, bezpieczeństwo, format danych, reguły walidacji, statusy i przykłady kodu.

Jak to działa Podpis żądania

Spis treści

  1. Jak to działa
  2. Zasady współpracy
  3. Dostęp i bezpieczeństwo
  4. Podpis żądania (HMAC)
  5. Dane z katalogu: cena, pakowanie, stan
  6. Format zamówienia
  7. Reguły walidacji i kody błędów
  8. Odpowiedzi i kody HTTP
  9. Statusy zamówienia
  10. Ponawianie i idempotencja
  11. Limity
  12. Testy i uruchomienie
  13. Najczęstsze problemy
  14. Kontakt

Zmiany w API opisuje historia zmian.

1. Jak to działa

Pięć kroków od katalogu do odbioru towaru. Bazowy adres API: https://api.akces.pl.

  1. Pobierz katalog — GET /v1/products. Dla każdego produktu dostajesz m.in. yourPriceNetto (Twoja cena — informacyjnie), orderStep (krok ilości wynikający z pakowania), minOrderQuantity (najmniejsza ilość) i availableStock (dostępność). Pełną listę odświeżaj co 15–30 minut (sekcja 5).
  2. Zbuduj zamówienie w swoim systemie: SKU i ilość będąca wielokrotnością orderStep, nie mniejsza niż minOrderQuantity. Ceny nie musisz podawać — zawsze obowiązuje cena Akces.
  3. Sprawdź je (zalecane) — POST /v1/orders/validate. Te same reguły co przy zamówieniu (produkty, pakowanie, dostępność, ceny), ale bez rezerwacji towaru i bez zapisu. W odpowiedzi dostajesz ceny, które zostałyby zastosowane, gdybyś złożył zamówienie w tej chwili, i listę ewentualnych błędów.
  4. Wyślij zamówienie — POST /v1/orders z podpisem HMAC. Odpowiedź przychodzi od razu: przyjęte (ACCEPTED), czeka na zatwierdzenie (PENDING_REVIEW) albo odrzucone (REJECTED) z pełną listą błędów.
  5. Śledź status — GET /v1/orders?updatedSince=…: numer zamówienia w systemie Akces, etap realizacji, gotowość do odbioru.

2. Zasady współpracy

Reguły handlowe, które obowiązują każde zamówienie złożone przez API.

TematZasada
KontoZamówienie jest zawsze składane na firmę przypisaną do Twojego klucza API. W zamówieniu nie podajesz NIP-u ani danych firmy — nie da się złożyć zamówienia na inne konto.
Płatność i dokumentPłatność przelewem, termin zgodnie z Twoimi warunkami handlowymi w Akces. Dokument sprzedaży: faktura VAT.
CenyZawsze obowiązuje cena Akces z chwili złożenia zamówienia (odpowiedź 201/202): cena katalogowa netto w PLN za jednostkę produktu (unit) z najkorzystniejszym dla Ciebie rabatem. Cena podana w zamówieniu (unitPriceNetto) nie jest wymagana ani porównywana z ceną Akces — zapisujemy ją wyłącznie informacyjnie. Ceny zastosowane w zamówieniu zwracamy w odpowiedzi. VAT doliczamy na fakturze.
RabatyNaliczamy automatycznie najkorzystniejszy rabat: stały rabat Twojego konta, aktywną promocję lub wyprzedaż albo ofertę przygotowaną dla Twojej firmy. Rabaty nie sumują się — obowiązuje najniższa cena. W zamówieniu nie podajesz rabatu. Aktualną cenę widzisz w katalogu jako yourPriceNetto.
Ilości i pakowanieZamawiana ilość musi być wielokrotnością najmniejszego opakowania produktu (orderStep) i nie mniejsza niż minOrderQuantity. Nie przyjmujemy ilości, które wymagałyby otwierania (dzielenia) opakowań. W kategorii Kwiaty sztuczne / Główki kwiatowe obowiązuje minimum 5 opakowań w kolorze — także dla produktów z pudełkiem (pudełko lub karton wystarcza, jeśli mieści co najmniej 5 opakowań).
DostępnośćMożesz zamówić najwyżej tyle, ile wynosi availableStock w chwili złożenia zamówienia — wartość z wcześniej pobranego katalogu mogła się w międzyczasie zmienić. Większe ilości uzgodnij z działem obsługi klienta. Towar rezerwujemy w chwili złożenia zamówienia — nie trafi już do innych zamówień składanych w sklepie B2B ani przez API. W wyjątkowych sytuacjach (np. sprzedaż bezpośrednio w magazynie) towaru może zabraknąć przed realizacją — wtedy dział obsługi klienta skontaktuje się z Tobą.
Całość albo nicJeśli choć jedna pozycja jest błędna, odrzucamy całe zamówienie i zwracamy listę wszystkich błędów. Nie zapisujemy zamówienia z pominięciem błędnych pozycji.
OdbiórZamówienia są przygotowywane do odbioru osobistego: Magazyn Akces, ul. Sławęcińska 3, 05-850 Macierzysz. API nie przyjmuje adresu wysyłki. Gotowość do odbioru sygnalizuje status realizacji DO_ODBIORU.
ZatwierdzanieW okresie startowym każde poprawne zamówienie przed skierowaniem do realizacji zatwierdza dział obsługi klienta Akces (status PENDING_REVIEW). Towar jest zarezerwowany już od złożenia zamówienia. Zatwierdzenie przez Akces (PENDING_REVIEW → ACCEPTED) nie zmienia cen ani rezerwacji. Status PENDING_REVIEW nie wygasa automatycznie; towar pozostaje zarezerwowany do decyzji Akces. Jeśli zamówienie zostanie odrzucone przez Akces, dostaniesz status DECLINED z podanym powodem.
Numer zamówieniaKażde zamówienie ma numer z Twojego systemu (externalOrderNumber). Służy do bezpiecznego ponawiania i pojawia się na dokumentach po stronie Akces.
Zmiany i anulowanieAPI nie zmienia ani nie anuluje złożonych zamówień. Zmianę lub anulowanie zgłoś do działu obsługi klienta, podając swój numer zamówienia (externalOrderNumber) albo numer Akces: akcesOrderNumber (np. CX/0412/09/26) lub erpOrderNumber (numer zamówienia sprzedaży, ZO, np. ZO/0123/09/26).

3. Dostęp i bezpieczeństwo

Składanie zamówień wymaga trzech zabezpieczeń naraz. Wszystkie dane dostępowe przekazujemy osobnymi kanałami.

ElementGdzieOpis
Klucz APInagłówek X-Partner-KeyFormat pk_ + 32 znaki hex. Wymagany w każdym żądaniu (katalog i zamówienia). Identyfikuje Twoją firmę.
Sekret podpisutylko do obliczenia X-SignatureFormat whsec_…. Służy do podpisania każdego żądania zamówienia (sekcja 4). Nigdy nie wysyłaj sekretu w żądaniu.
Lista dozwolonych adresów IPpo stronie AkcesPrzyjmujemy wszystkie żądania z Twoim kluczem (katalog i zamówienia) tylko z uzgodnionych adresów IP Twoich serwerów. Żądanie z innego adresu dostaje 403 FORBIDDEN. Zmianę adresów zgłoś z wyprzedzeniem na kontakt@akces.pl.

Zasady

4. Podpis żądania (HMAC)

Wymagany dla POST /v1/orders i POST /v1/orders/validate. Dzięki podpisowi sam wyciek klucza nie pozwala złożyć zamówienia, a przechwyconego żądania nie da się użyć po upływie 5 minut (okno X-Timestamp); w tym oknie powtórka nie utworzy duplikatu dzięki idempotencji po externalOrderNumber.

Algorytm

  1. Przygotuj body jako tekst JSON (UTF-8) — dokładnie ten tekst wyślesz w żądaniu.
  2. Weź aktualny czas Unix w sekundach, np. 1790172000. Wyślesz go w nagłówku X-Timestamp.
  3. Zbuduj tekst do podpisu: <X-Timestamp>.<ścieżka>.<body>, gdzie ścieżka to /v1/orders albo /v1/orders/validate (bez domeny i parametrów).
  4. Policz HMAC-SHA256 tego tekstu z sekretem podpisu jako kluczem; wynik zapisz szesnastkowo małymi literami.
  5. Wyślij nagłówek X-Signature: sha256=<wynik>.
X-Partner-Key: pk_…
X-Timestamp: 1790172000
X-Signature: sha256=hex( HMAC_SHA256( sekret, "1790172000" + "." + "/v1/orders" + "." + body ) )
Content-Type: application/json

Okno czasowe: znacznik czasu może różnić się od naszego zegara najwyżej o 300 sekund. Zsynchronizuj zegar serwera przez NTP. Przy ponowieniu żądania policz nowy znacznik czasu i nowy podpis.

Ścieżka w podpisie powoduje, że podpisu walidacji próbnej nie da się użyć do złożenia prawdziwego zamówienia.

Wektor testowy

Sprawdź swoją implementację na tych danych — musisz otrzymać dokładnie podany podpis.

DaneWartość
Sekretwhsec_przyklad_do_testow
X-Timestamp1790172000
Ścieżka/v1/orders/validate
Body{"externalOrderNumber":"TEST-0001","currency":"PLN","lines":[{"lineNo":1,"sku":"A02-003","quantity":12}]}
Tekst do podpisu1790172000./v1/orders/validate.{"externalOrderNumber":"TEST-0001","currency":"PLN","lines":[{"lineNo":1,"sku":"A02-003","quantity":12}]}
X-Signaturesha256=2b006a5388444a9866f730c8e7ac22e05d19b908a58055f4970a67e78f845c37

Przykłady

# bash + openssl — walidacja próbna
BODY='{"externalOrderNumber":"TEST-0001","currency":"PLN","lines":[{"lineNo":1,"sku":"A02-003","quantity":12}]}'
TS=$(date +%s)
SIG=$(printf '%s' "$TS./v1/orders/validate.$BODY" | openssl dgst -sha256 -hmac "$AKCES_SIGNING_SECRET" | sed 's/^.* //')

curl -sS -X POST "https://api.akces.pl/v1/orders/validate" \
  -H "Content-Type: application/json" \
  -H "X-Partner-Key: $AKCES_API_KEY" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: sha256=$SIG" \
  --data-binary "$BODY"        # --data-binary: wysyła dokładnie te bajty
// Node.js 18+ (fetch wbudowany)
import crypto from "node:crypto";

async function sendOrder(order, path = "/v1/orders") {
  const body = JSON.stringify(order);                 // ten sam tekst podpisujemy i wysyłamy
  const ts = String(Math.floor(Date.now() / 1000));
  const signature = crypto
    .createHmac("sha256", process.env.AKCES_SIGNING_SECRET)
    .update(ts + "." + path + "." + body, "utf8")
    .digest("hex");

  const res = await fetch("https://api.akces.pl" + path, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Partner-Key": process.env.AKCES_API_KEY,
      "X-Timestamp": ts,
      "X-Signature": "sha256=" + signature,
    },
    body,
  });
  return { status: res.status, json: await res.json() };
}
// PHP 8+ z rozszerzeniem curl
function sendOrder(array $order, string $path = "/v1/orders"): array {
    $body = json_encode($order, JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION);
    $ts = (string) time();
    $signature = hash_hmac("sha256", $ts . "." . $path . "." . $body, getenv("AKCES_SIGNING_SECRET"));

    $ch = curl_init("https://api.akces.pl" . $path);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $body,          // dokładnie ten sam tekst, który podpisano
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 90,
        CURLOPT_HTTPHEADER => [
            "Content-Type: application/json",
            "X-Partner-Key: " . getenv("AKCES_API_KEY"),
            "X-Timestamp: " . $ts,
            "X-Signature: sha256=" . $signature,
        ],
    ]);
    $response = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return ["status" => $status, "json" => json_decode($response, true)];
}
# Python 3 + requests
import hashlib, hmac, json, os, time, requests

def send_order(order, path="/v1/orders"):
    body = json.dumps(order, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
    ts = str(int(time.time()))
    message = ts.encode() + b"." + path.encode() + b"." + body
    signature = hmac.new(os.environ["AKCES_SIGNING_SECRET"].encode(), message, hashlib.sha256).hexdigest()
    r = requests.post("https://api.akces.pl" + path, data=body, timeout=90, headers={
        "Content-Type": "application/json",
        "X-Partner-Key": os.environ["AKCES_API_KEY"],
        "X-Timestamp": ts,
        "X-Signature": "sha256=" + signature,
    })
    return r.status_code, r.json()
// .NET 6+ (System.Text.Json, HttpClient)
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

// Nazwy pól jak w API (camelCase), puste pola opcjonalne pomijamy
static readonly JsonSerializerOptions AkcesJson = new()
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};

static async Task<HttpResponseMessage> SendOrder(HttpClient http, object order, string path = "/v1/orders")
{
    var body = JsonSerializer.Serialize(order, AkcesJson);
    var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("AKCES_SIGNING_SECRET")!));
    var signature = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(ts + "." + path + "." + body))).ToLowerInvariant();

    var request = new HttpRequestMessage(HttpMethod.Post, "https://api.akces.pl" + path)
    {
        Content = new StringContent(body, Encoding.UTF8, "application/json")
    };
    request.Headers.Add("X-Partner-Key", Environment.GetEnvironmentVariable("AKCES_API_KEY"));
    request.Headers.Add("X-Timestamp", ts);
    request.Headers.Add("X-Signature", "sha256=" + signature);
    return await http.SendAsync(request);
}

Najczęstsze błędy podpisu

5. Dane z katalogu: cena, pakowanie, stan

Zamówienie budujesz na podstawie GET /v1/products (przykład z komentarzami: /docs#przyklad-produktu; pełny schemat: Swagger UI). Dla zamówień kluczowe są:

PoleZnaczenie dla zamówienia
skuKod produktu (wariantu), który podajesz w pozycji zamówienia.
eanKod kreskowy. Możesz go dołączyć do pozycji jako kontrolę — jeśli nie pasuje do SKU, całe zamówienie zostanie odrzucone (błąd EAN_MISMATCH w tej pozycji). Chroni to przed błędnym mapowaniem produktów po Twojej stronie.
unitJednostka, w której podajesz ilość i w której liczona jest cena (np. szt, opak). Nie przeliczamy jednostek.
yourPriceNettoTwoja aktualna cena netto za jednostkę (po rabacie konta / promocji / wyprzedaży / ofercie) — po tej cenie zostanie złożone zamówienie, jeśli nie zmieni się do chwili jego złożenia (promocje i oferty mogą ją zmienić bez zmiany updatedAt); aktualną sprawdzisz przez POST /v1/orders/validate. null, gdy produktu nie można zamówić przez API albo warunki są chwilowo niedostępne — wtedy availableStock i orderStep są orientacyjne; sprawdź koszyk przez POST /v1/orders/validate.
priceNettoCena katalogowa przed rabatem — informacyjnie.
packagingIlości w opakowaniu (package), pudełku (innerCarton) i kartonie zbiorczym (outerCarton), w jednostce produktu.
orderStepLiczba jednostek w najmniejszym zdefiniowanym opakowaniu; ilość w zamówieniu musi być jej wielokrotnością. 1 = bez ograniczeń.
minOrderQuantityNajmniejsza ilość, jaką możesz zamówić. Zwykle równa orderStep; w Główkach kwiatowych = 5 opakowań (5 × packaging.package), także dla produktów z pudełkiem.
availableStockMaksymalna ilość, jaką możesz zamówić (patrz niżej).

Pakowanie — przykład

Produkt ma opakowanie po 5 szt., pudełko po 20 szt. i karton po 100 szt., więc orderStep = 5. Możesz zamówić dowolną wielokrotność 5: 5, 10, 15, 20, 25… Zamówienie na 9 szt. zostanie odrzucone z podpowiedzią najbliższych poprawnych ilości (5 i 10). Podobnie B14-01: pudełko po 96 szt. i karton po 1152 szt., więc orderStep = 96.

Główki kwiatowe — minimum 5 opakowań w kolorze: B101-01 (opakowanie 12, pudełko 108, karton 432) ma orderStep = 12 i minOrderQuantity = 60 — możesz zamówić 60, 72, 84… (np. 5 opakowań, całe pudełko 108 albo karton 432), ale nie 24 (błąd BELOW_MINIMUM_QUANTITY). Tak samo D77-01 (opakowanie 12, karton 720): orderStep = 12, minOrderQuantity = 60.

Stan

availableStock to maksymalna ilość produktu, jaką możesz zamówić. Stan jest zamaskowany: poniżej 5 jednostek pokazujemy 0, a powyżej dwóch kartonów zbiorczych — 2× outerCarton (dla produktów bez zdefiniowanego kartonu zbiorczego stan nie jest ograniczany do 2× karton). Wynik zaokrąglamy w dół do wielokrotności orderStep (np. 10 przy kroku 6 → 6); jeśli jest mniejszy niż minOrderQuantity, pokazujemy 0. Produkty, których nie można zamówić przez API (np. bez ceny katalogowej albo zablokowane), mają availableStock 0 i yourPriceNetto null.

Zamówienie na większą ilość zostanie odrzucone (INSUFFICIENT_STOCK, w details.maxOrderable podajemy ilość możliwą do zamówienia). Tę samą regułę stosuje walidacja próbna. Jeśli potrzebujesz większej ilości, skontaktuj się z działem obsługi klienta.

Aktualność danych

Parametr updatedSince i pole updatedAt obejmują zmiany kartoteki, stanów, cen katalogowych i zdjęć. Nie obejmują yourPriceNetto (promocje, oferty, rabat konta) ani rezerwacji wynikających z zamówień innych klientów. Dlatego:

Ceny w zamówieniu

Cenę narzuca Akces: w chwili złożenia zamówienia naliczamy cenę katalogową z najkorzystniejszym rabatem (konto / promocja / wyprzedaż / oferta). Ceny i dostępność zmieniają się na bieżąco (zmiana ceny katalogowej, początek lub koniec promocji albo oferty, zamówienia innych klientów), dlatego yourPriceNetto i availableStock z wcześniej pobranego katalogu mogą się różnić od wartości w chwili złożenia zamówienia. Ceny zastosowane w zamówieniu zawsze zwracamy w odpowiedzi (unitPriceNetto, discountPercent, lineTotalNetto, totalNetto) — zapisz je w swoim systemie. Aktualne ceny i dostępność przed wysłaniem pokaże POST /v1/orders/validate.

6. Format zamówienia

POST /v1/orders (i /v1/orders/validate), treść żądania (body) JSON w UTF-8, maks. 256 KB. Nieznane pola są błędem — literówka w nazwie pola nie przejdzie niezauważona.

PoleWymaganeZasady
externalOrderNumbertakNumer zamówienia w Twoim systemie: 1–50 znaków — litery łacińskie bez polskich znaków, cyfry oraz / . _ -, pierwszy znak to litera lub cyfra; spacje na brzegach są usuwane. Numer jest zablokowany przez zamówienia ACCEPTED i PENDING_REVIEW; po REJECTED i DECLINED można go użyć ponownie. Służy do idempotencji (sekcja 10).
currencytakZawsze "PLN".
linestakPozycje zamówienia: od 1 do 500. Każdy SKU najwyżej raz.
lines[].skutakKod produktu z katalogu (1–64 znaki).
lines[].quantitytakLiczba całkowita od 1 do 999 999, wielokrotność orderStep, co najmniej minOrderQuantity, najwyżej availableStock.
lines[].unitPriceNettonieLiczba (jeśli podana) od 0 do 1 000 000 albo null. Cena netto z Twojego systemu — tylko informacyjnie, nie jest porównywana z ceną Akces. Obowiązuje cena Akces.
lines[].lineNonieNumer linii w Twoim systemie (liczba całkowita od 1 do 100 000). Wraca w odpowiedzi i w błędach — ułatwia wskazanie pozycji. Bez niego numerujemy pozycje kolejno od 1.
lines[].eannie8–14 cyfr. Kontrola krzyżowa z kartoteką produktu.
notesnieUwagi do zamówienia, do 1000 znaków. Trafiają na zamówienie w systemie Akces.
contactnieOsoba kontaktowa w sprawie zamówienia / odbioru: name (do 120 znaków), phone (do 40 znaków), email (poprawny adres, do 200 znaków).
expectedTotalNettonieLiczba (jeśli podana) od 0 do 1 000 000 albo null. Wartość zamówienia z Twojego systemu — tylko informacyjnie, nie jest porównywana z wartością Akces.

Nazwy pól podawaj dokładnie jak w tabeli (camelCase) — nieznane pola dają 400. Puste pola opcjonalne najlepiej pomijaj; wartość null przyjmujemy w polach notes, contact, contact.name, contact.phone, contact.email, lines[].lineNo, lines[].ean, lines[].unitPriceNetto i expectedTotalNetto.

Przykład

{
  "externalOrderNumber": "FK/2026/000123",
  "currency": "PLN",
  "notes": "Odbiór we wtorek rano",
  "contact": { "name": "Jan Nowak", "phone": "+48 600 100 200", "email": "magazyn@example.com" },
  "lines": [
    { "lineNo": 1, "sku": "A02-003", "ean": "5907489847698", "quantity": 12 },
    { "lineNo": 2, "sku": "B14-01", "quantity": 96 }
  ]
}

7. Reguły walidacji i kody błędów

Sprawdzamy wszystkie reguły naraz i zwracamy pełną listę błędów z numerem linii (lineNo) i SKU. Jeden błąd odrzuca całe zamówienie.

KodCo oznaczaCo zrobić
UNKNOWN_SKUSKU nie istnieje w katalogu Akces.Sprawdź mapowanie produktów; odśwież katalog.
PRODUCT_NOT_AVAILABLEProdukt niedostępny w sprzedaży przez API, np. bez ceny katalogowej albo zablokowany (nie występuje w GET /v1/products albo ma tam yourPriceNetto null i availableStock 0; mógł zniknąć od ostatniego pobrania). Sam brak stanu daje INSUFFICIENT_STOCK z maxOrderable 0.Usuń pozycję; odśwież katalog.
EAN_MISMATCHPodany EAN nie należy do SKU. W details.sentEan — wysłany EAN.Popraw mapowanie EAN ↔ SKU po swojej stronie.
DUPLICATE_LINETen sam SKU występuje w zamówieniu więcej niż raz.Połącz ilości w jedną pozycję.
PACKAGING_MULTIPLEIlość nie jest wielokrotnością orderStep. W details: orderStep, packaging, suggestedQuantities.Zaokrąglij ilość do jednej z podpowiedzianych.
BELOW_MINIMUM_QUANTITYIlość jest mniejsza niż minOrderQuantity (minimalne ilości w kolorze — Główki kwiatowe). W details: minOrderQuantity, orderStep.Zwiększ ilość do co najmniej minOrderQuantity albo usuń pozycję.
INSUFFICIENT_STOCKIlość przekracza availableStock. W details.maxOrderable — ilość, którą możesz zamówić.Zmniejsz ilość do maxOrderable albo usuń pozycję.
CONTRACTOR_BLOCKEDKonto chwilowo nie może składać zamówień.Skontaktuj się z działem obsługi klienta.

Przykład odrzucenia (HTTP 422)

{
  "error": "Order rejected. See errors for details.",
  "code": "ORDER_REJECTED",
  "data": {
    "id": "cmf2xa1b20002abcd1234efgh",
    "externalOrderNumber": "FK/2026/000124",
    "status": "REJECTED",
    …                                                  // skrócone — pozostałe pola jak w odpowiedzi 202 (sekcja 8); dla REJECTED akcesOrderNumber i fulfillmentStatus mają wartość null
    "errors": [
      { "lineNo": 1, "sku": "A02-003", "code": "INSUFFICIENT_STOCK",
        "message": "Quantity of A02-003 exceeds the available quantity (availableStock).",
        "details": { "maxOrderable": 240 } },
      { "lineNo": 2, "sku": "B14-01", "code": "PACKAGING_MULTIPLE",
        "message": "Quantity 100 of B14-01 is not a multiple of the packaging unit (96).",
        "details": { "orderStep": 96, "packaging": { "package": null, "innerCarton": 96, "outerCarton": 1152 },
                     "suggestedQuantities": [96, 192] } }
    ]
  },
  "timestamp": "2026-09-24T08:20:00.095Z"
}

Odrzucone zamówienie nie rezerwuje towaru. Popraw błędy i wyślij je ponownie — możesz użyć tego samego externalOrderNumber.

8. Odpowiedzi i kody HTTP

Każda odpowiedź to JSON. Błędy mają pola error (opis) i code (kod do obsługi w programie).

HTTPKodZnaczenie
201—Zamówienie przyjęte do realizacji (status: ACCEPTED), towar zarezerwowany.
202—Zamówienie poprawne, towar zarezerwowany, czeka na zatwierdzenie przez Akces (status: PENDING_REVIEW).
200—Powtórzone żądanie z tym samym numerem i treścią — dostajesz aktualny stan pierwotnego zamówienia (idempotentReplay: true), nowe zamówienie nie powstaje. Walidacja próbna też zwraca 200.
400VALIDATION_ERROR, INVALID_JSONBody niezgodne ze schematem (lista w details: pole i opis), niepoprawny JSON albo niepoprawny parametr zapytania (np. updatedSince). Zamówienie nie jest zapisywane.
401UNAUTHORIZED, SIGNATURE_MISSING, SIGNATURE_INVALID, TIMESTAMP_INVALID, TIMESTAMP_EXPIREDBrak lub zły klucz API, brak lub zły podpis, znacznik czasu niebędący liczbą sekund Unix albo spoza okna ±300 s.
403FORBIDDEN, ORDERS_DISABLED, BROWSER_NOT_ALLOWED, HTTPS_REQUIREDAdres IP spoza listy dozwolonych, zamówienia przez API niewłączone dla konta, żądanie z przeglądarki, żądanie przez http:// zamiast https://.
404NOT_FOUNDZamówienie o podanym id nie istnieje albo nie należy do Twojej firmy (GET /v1/orders/{id}); także nieznany adres pod /v1.
405METHOD_NOT_ALLOWEDMetoda HTTP nieobsługiwana pod tym adresem (np. GET /v1/orders/validate). Dozwolone metody podaje nagłówek Allow.
409DUPLICATE_ORDER_NUMBERTen numer jest już użyty w zamówieniu ACCEPTED lub PENDING_REVIEW, a treść jest inna.
413PAYLOAD_TOO_LARGEBody większe niż 256 KB — podziel zamówienie.
415UNSUPPORTED_MEDIA_TYPEBrak nagłówka Content-Type albo wartość inna niż application/json.
422ORDER_REJECTEDZamówienie odrzucone przez reguły biznesowe — pełna lista w data.errors.
429RATE_LIMITED, DAILY_LIMIT_EXCEEDEDPrzekroczony limit (sekcja 11). Odczekaj czas z nagłówka Retry-After (przy DAILY_LIMIT_EXCEEDED także w polu retryAfterSeconds).
5xx (500, 502, 503, 504)INTERNAL_ERROR, SERVICE_UNAVAILABLEChwilowy problem po stronie Akces; 503 ma nagłówek Retry-After. Ponów z tym samym numerem zamówienia i treścią (sekcja 10).

Przykład odpowiedzi 202 (czeka na zatwierdzenie)

{
  "data": {
    "id": "cmf2x9k0a0001abcd1234efgh",          // zapisz — do GET /v1/orders/{id}
    "externalOrderNumber": "FK/2026/000123",
    "status": "PENDING_REVIEW",
    "erpOrderNumber": null,                          // numer ZO — po skierowaniu do realizacji
    "akcesOrderNumber": "CX/0412/09/26",           // numer Akces — nadawany od razu
    "fulfillmentStatus": null,                       // null do czasu przyjęcia (ACCEPTED)
    "currency": "PLN",
    "totalNetto": 164.04,
    "lines": [                                     // ceny Akces zastosowane w zamówieniu
      { "lineNo": 1, "sku": "A02-003", "quantity": 12, "unitPriceNetto": 7.67,
        "basePriceNetto": 10.95, "discountPercent": 30, "discountSource": "company", "lineTotalNetto": 92.04 },
      { "lineNo": 2, "sku": "B14-01", "quantity": 96, "unitPriceNetto": 0.75,
        "basePriceNetto": 1.5, "discountPercent": 50, "discountSource": "promotion", "lineTotalNetto": 72 }
    ],
    "errors": [],
    "declineReason": null,
    "createdAt": "2026-09-24T08:15:00.000Z",
    "updatedAt": "2026-09-24T08:15:00.000Z"
  },
  "timestamp": "2026-09-24T08:15:00.120Z"
}

Ceny i rabaty w tym przykładzie są umowne.

Numery zamówienia w odpowiedzi

9. Statusy zamówienia

Zamówienie ma dwa poziomy statusu: status (decyzja o przyjęciu) i — dla zamówień przyjętych (ACCEPTED) — fulfillmentStatus (etap realizacji w Akces).

status

WartośćZnaczenie
PENDING_REVIEWZamówienie poprawne, towar zarezerwowany, czeka na zatwierdzenie przez dział obsługi klienta Akces. Status nie wygasa automatycznie.
ACCEPTEDZamówienie przyjęte do realizacji. fulfillmentStatus jest ustawiany od razu (NOWE); erpOrderNumber (numer ZO) pojawi się po przekazaniu zamówienia do realizacji.
REJECTEDOdrzucone automatycznie przez reguły walidacji — lista w errors. Towar nie był rezerwowany.
DECLINEDOdrzucone przez Akces po weryfikacji — powód w declineReason. Rezerwacja została zwolniona.

fulfillmentStatus (tylko dla ACCEPTED)

WartośćZnaczenie
NOWEZamówienie przyjęte, czeka na przekazanie do realizacji.
DO_REALIZACJIPrzekazane do realizacji, czeka na kompletację.
W_REALIZACJIW trakcie realizacji: kompletowanie, kontrola i inne etapy pośrednie w magazynie.
WSTRZYMANERealizacja wstrzymana — w razie wątpliwości skontaktuj się z działem obsługi klienta.
DO_ODBIORUGotowe do odbioru w magazynie Akces.
WYSLANETowar wydany z magazynu (wysłany).
ODEBRANETowar odebrany.
CZESCIOWO_ZREALIZOWANEZamówienie zrealizowane częściowo.
ZREALIZOWANEZamówienie zrealizowane w całości.
ANULOWANEZamówienie anulowane lub usunięte w systemie Akces (zwykle po uzgodnieniu z Tobą). Zamówienie już zakończone zachowuje swój ostatni status.

fulfillmentStatus ma wartość null, dopóki zamówienie nie ma statusu ACCEPTED (PENDING_REVIEW, REJECTED, DECLINED). Lista może zostać rozszerzona o kolejne wartości — nieznaną wartość traktuj jak W_REALIZACJI.

Śledzenie statusów

10. Ponawianie i idempotencja

externalOrderNumber jest kluczem idempotencji — dzięki niemu ponowienie nigdy nie tworzy duplikatu.

SytuacjaCo robimy
Ten sam numer i ta sama treść, zamówienie ACCEPTED lub PENDING_REVIEWZwracamy aktualny stan pierwotnego zamówienia (HTTP 200, idempotentReplay: true). Nie powstaje nowe zamówienie.
Ten sam numer, inna treść, zamówienie ACCEPTED lub PENDING_REVIEWHTTP 409 DUPLICATE_ORDER_NUMBER. Zmiany w takim zamówieniu — tylko przez dział obsługi klienta.
Ten sam numer po odrzuceniu (REJECTED / DECLINED)Traktujemy jak nowe zamówienie — możesz wysłać poprawioną wersję pod tym samym numerem.

„Ta sama treść” = te same pozycje w tej samej kolejności (sku, quantity, lineNo, ean), te same uwagi (notes), kontakt (contact) i waluta. Ceny informacyjne (unitPriceNetto, expectedTotalNetto) nie są porównywane — ponowienie z inną ceną informacyjną to nadal powtórka (HTTP 200). Przy ponowieniu najlepiej wysyłaj dokładnie ten sam JSON co za pierwszym razem — zapisz go przed wysłaniem.

Zalecany sposób ponawiania

  1. Przekroczony czas oczekiwania (timeout), błąd sieci, HTTP 500/502/504: ponów z tym samym externalOrderNumber i tą samą treścią, z nowym znacznikiem czasu i podpisem. Po 502/504 i po przekroczeniu czasu zamówienie mogło już zostać złożone — ponowienie zwróci wtedy 200 z idempotentReplay: true. Odstępy: np. 5 s, 30 s, 2 min, 10 min.
  2. HTTP 429 i 503: odczekaj co najmniej czas z nagłówka Retry-After, potem ponawiaj z rosnącymi odstępami (np. 2 min, 10 min) — przy 503 z tym samym externalOrderNumber i treścią.
  3. HTTP 400/401/403/404/405/409/413/415/422: nie ponawiaj bez zmian — popraw żądanie lub konfigurację.
  4. Jeśli nie masz pewności, czy zamówienie dotarło, sprawdź GET /v1/orders?externalOrderNumber=…. Możesz dostać kilka prób z tym numerem (np. odrzuconą i poprawioną); obowiązuje ta ze statusem ACCEPTED lub PENDING_REVIEW.
  5. Ustaw timeout żądania na co najmniej 90 s.

11. Limity

OperacjaLimit
POST /v1/orders10 żądań na minutę
POST /v1/orders/validate30 żądań na minutę
GET /v1/orders, GET /v1/orders/{id}60 żądań na minutę
GET /v1/products i GET /v1/products/{sku} (wspólny limit)Limit ustalony dla konta (domyślnie 60 żądań na minutę)
Wielkość zamówieniaMaks. 500 pozycji i 256 KB body
Próby zamówień500 prób w ciągu ostatnich 24 h (okno kroczące; złożone i odrzucone łącznie)
Nieudane uwierzytelnienie (zły lub brakujący klucz)30 na minutę z jednego adresu IP

Po przekroczeniu limitu dostajesz HTTP 429 z nagłówkiem Retry-After (liczba sekund do ponowienia):

Jeśli limity są za niskie dla Twojej skali, napisz na kontakt@akces.pl.

12. Testy i uruchomienie

Nie ma osobnego środowiska testowego — testuj na produkcyjnym katalogu, cenach i stanach przez walidację próbną.

Lista kontrolna przed startem

13. Najczęstsze problemy

401 SIGNATURE_INVALID mimo poprawnego sekretu

Najczęściej podpisany tekst różni się od wysłanego body (ponowna serializacja JSON przez bibliotekę HTTP, inne formatowanie, BOM) albo ścieżka w podpisie nie odpowiada adresowi. Porównaj swoją implementację z wektorem testowym i wysyłaj body jako gotowy tekst.

401 TIMESTAMP_EXPIRED / TIMESTAMP_INVALID

TIMESTAMP_EXPIRED — zegar serwera różni się od naszego o ponad 300 s: włącz synchronizację NTP. TIMESTAMP_INVALID — X-Timestamp nie jest liczbą sekund Unix (np. wysyłasz milisekundy, 13 cyfr): używaj sekund.

403 FORBIDDEN (adres IP spoza listy)

Żądanie wyszło z adresu IP spoza listy dozwolonych adresów (np. nowy serwer, zmiana dostawcy internetu, VPN). Zgłoś nowe adresy na kontakt@akces.pl.

Cena w zamówieniu różni się od ceny w moim systemie

Obowiązuje cena Akces z chwili złożenia zamówienia — w międzyczasie mogła zmienić się cena katalogowa albo zacząć się lub skończyć promocja czy oferta. Ceny zastosowane w zamówieniu są w odpowiedzi (lines[].unitPriceNetto, totalNetto) oraz w GET /v1/orders. Odśwież katalog, żeby mieć aktualne yourPriceNetto, a przed wysłaniem sprawdź koszyk przez POST /v1/orders/validate.

422 INSUFFICIENT_STOCK, choć katalog pokazywał stan

Dostępność zmienia się na bieżąco — inni klienci też zamawiają, a katalog mógł zostać pobrany wcześniej. Zmniejsz ilość do details.maxOrderable albo usuń pozycję. Większe ilości niż availableStock uzgodnij z działem obsługi klienta.

Nie wiem, czy zamówienie dotarło (przekroczony czas oczekiwania)

Wyślij je ponownie z tym samym numerem i treścią albo sprawdź GET /v1/orders?externalOrderNumber=…. Duplikat nie powstanie.

Chcę zmienić lub anulować złożone zamówienie

Skontaktuj się z działem obsługi klienta, podając externalOrderNumber, akcesOrderNumber albo erpOrderNumber. Wysłanie tego samego numeru z inną treścią zwróci 409.

14. Kontakt

Dział obsługi klienta

Zamówienia, zmiany, anulowanie, odbiór, większe ilości

hurtownia@akces.pl
tel. 22 721 18 30

Odbiór osobisty: Magazyn Akces, ul. Sławęcińska 3, 05-850 Macierzysz.

Podaj externalOrderNumber, akcesOrderNumber albo erpOrderNumber.

Wsparcie techniczne API

Integracja, błędy, dostęp, klucze, adresy IP

kontakt@akces.pl

W zgłoszeniu podaj: externalOrderNumber lub id zamówienia, czas żądania, kod HTTP i treść odpowiedzi.

Nigdy nie przesyłaj w zgłoszeniu klucza API ani sekretu podpisu.

Specyfikacja techniczna: OpenAPI 3.0 · Swagger UI · Dokumentacja katalogu produktów · Historia zmian.