Przejdź do treści
Premium Supply Developer

Uwierzytelnianie

OAuth 2.0 — jako dostawca, z zakresami i PKCE

Seller API wydaje tokeny w dwóch przepływach: client_credentials dla skryptów działających na Twoim koncie oraz authorization_code + PKCE S256 dla aplikacji, którym dostęp przyznają inni sprzedawcy. Token dostępowy żyje 12 godzin, odświeżający 90 dni, kod autoryzacyjny 10 minut. Refresh token rotuje przy każdym użyciu.

Który przepływ wybrać#

SytuacjaPrzepływCo dostajesz
Piszę skrypt lub integrację na własne konto sprzedawcy (ERP, synchronizacja cen, wysyłki).client_credentialsAccess token na konto właściciela aplikacji. Bez refresh tokenu — po wygaśnięciu pobierasz nowy tą samą parą.
Buduję aplikację, z której będą korzystać inni sprzedawcy (SaaS, wtyczka do sklepu, hurtownia).authorization_code + PKCEAccess + refresh token per sprzedawca, po jego zgodzie na ekranie premiumsupply.pl. Zakresy widoczne przy zgodzie.

Nie obsługujemy przepływów implicit, password ani device_code. Punkt końcowy tokenów jest wspólny: POST https://api.premiumsupply.pl/oauth/token, ciało application/x-www-form-urlencoded (akceptujemy też JSON).

Identyfikatory i prefiksy#

Każdy sekret ma czytelny prefiks — po samej wartości widać w logach i skanerach wycieków, co to jest. Tokeny są nieprzezroczyste (32 losowe bajty, w bazie tylko SHA-256) — nie są JWT i nie da się z nich nic odczytać.

PrefiksCo toGdzie występuje
psa_client_id aplikacjiPanel aplikacji, Basic auth, parametr client_id
pss_client_secretPokazany raz przy rejestracji i przy rotacji. Nigdy w przeglądarce ani w repozytorium.
ps_at_access token (12 h)Authorization: Bearer ps_at_…
ps_rt_refresh token (90 dni, jednorazowy)Tylko w odpowiedzi /oauth/token dla authorization_code i refresh_token
ps_ac_kod autoryzacyjny (10 min, jednorazowy)Parametr code na redirect_uri

client_credentials — token na własne konto#

Przepływ client_credentialsTwoja aplikacjaapi.premiumsupply.pl1POST /oauth/token · Basic(client_id:client_secret)2200 { access_token, expires_in: 43200, scope }3GET /v1/… · Bearer ps_at_… · User-Agent4200 … / 401 unauthorized po 12 h

Przepływ client_credentials

  1. Twoja aplikacjaapi.premiumsupply.pl: grant_type=client_credentials, opcjonalnie scope (podzbiór zakresów aplikacji). Uwierzytelnienie klienta nagłówkiem Basic albo polami client_id/client_secret w ciele.
  2. api.premiumsupply.plTwoja aplikacja: Access token ważny 12 h na konto właściciela aplikacji. Brak refresh tokenu.
  3. Twoja aplikacjaapi.premiumsupply.pl: Zwykłe żądania z tokenem. Każda odpowiedź niesie X-Trace-Id i X-RateLimit-*.
  4. api.premiumsupply.plTwoja aplikacja: Po wygaśnięciu 401 unauthorized — pobierz nowy token krokiem 1. Lepiej odnawiać prewencyjnie, przed upływem expires_in.
Żądanie
curl -s -X POST https://api.premiumsupply.pl/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "scope=offers:read offers:write"
Odpowiedź 200
{
  "access_token": "ps_at_Qm9ndXNfdG9rZW5fZG9rdW1lbnRhY2ppXzEyMzQ1Njc4OTA",
  "token_type": "Bearer",
  "expires_in": 43200,
  "scope": "offers:read offers:write"
}
  • Basic auth: base64(client_id:client_secret). Alternatywnie client_id i client_secret jako pola formularza — ale nie w URL.
  • scope jest opcjonalny; pominięty = wszystkie zakresy aplikacji. Zakres spoza aplikacji → 400 invalid_scope.
  • Aplikacja o statusie suspended nie dostaje tokenów (401 invalid_client), a tokeny już wydane przestają działać.

authorization_code + PKCE — dostęp do kont innych sprzedawców#

Sprzedawca loguje się na premiumsupply.pl, widzi nazwę Twojej aplikacji, listę zakresów słowami (nie kodami) oraz host, na który wróci, i klika „Zezwól”. Wracasz z jednorazowym kodem, który wymieniasz na parę tokenów. PKCE (S256) jest obowiązkowe — także dla aplikacji serwerowych z sekretem.

Przepływ authorization_code z PKCE S256Twoja aplikacjaPrzeglądarka sprzedawcyapi.premiumsupply.plpremiumsupply.pl (zgoda)1302 → /oauth/authorize2GET /oauth/authorize?…3302 → /oauth/consent4ekran zgody: „Zezwól” / „Odmów”5302 → redirect_uri?code&state6callback: code + state7POST /oauth/token (code, code_verifier)8200 { access_token, refresh_token }

Przepływ authorization_code z PKCE S256

  1. Twoja aplikacjaPrzeglądarka sprzedawcy: Generujesz code_verifier, code_challenge i state (zapisane w sesji), po czym przekierowujesz przeglądarkę na /oauth/authorize z parametrami z tabeli niżej.
  2. Przeglądarka sprzedawcyapi.premiumsupply.pl: API sprawdza client_id, redirect_uri (musi być zarejestrowany), scope, state, code_challenge. Złe client_id/redirect_uri → 400 w JSON bez przekierowania; inne błędy wracają na redirect_uri jako ?error=…
  3. api.premiumsupply.plpremiumsupply.pl (zgoda): Przekierowanie na ekran zgody w storefroncie — tam żyje sesja sprzedawcy. Niezalogowany sprzedawca najpierw się loguje.
  4. premiumsupply.pl (zgoda)Przeglądarka sprzedawcy: Sprzedawca widzi nazwę aplikacji, opis, link do dokumentacji, zakresy z opisami i host powrotu. Zgoda wiąże aplikację z podmiotem sprzedającym, nie z loginem.
  5. premiumsupply.pl (zgoda)Przeglądarka sprzedawcy: Kod ps_ac_… ważny 10 minut, jednorazowy. Odmowa → ?error=access_denied&state=…
  6. Przeglądarka sprzedawcyTwoja aplikacja: Porównaj state z zapisanym w sesji — różny state = przerwij (ochrona przed CSRF).
  7. Twoja aplikacjaapi.premiumsupply.pl: grant_type=authorization_code, code, redirect_uri (identyczny jak w kroku 1), code_verifier. Klient z sekretem uwierzytelnia się Basic; klient publiczny może wymienić kod samym PKCE.
  8. api.premiumsupply.plTwoja aplikacja: Access 12 h + refresh 90 dni. Zapisz oba atomowo w kontekście sprzedawcy.

Krok 1: PKCE i state#

Node.js
import { createHash, randomBytes } from "node:crypto";

// code_verifier: 43–128 znaków z zestawu [A-Za-z0-9-._~]; 32 losowe bajty → 43 znaki base64url
const codeVerifier = randomBytes(32).toString("base64url");

// code_challenge = base64url(SHA-256(code_verifier)) — zawsze 43 znaki
const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");

// state: min. 8 znaków, losowy, zapisany w sesji użytkownika do porównania po powrocie
const state = randomBytes(16).toString("base64url");

Krok 2: przekierowanie na /oauth/authorize#

Adres przekierowania (złamany dla czytelności)
GET https://api.premiumsupply.pl/oauth/authorize
    ?response_type=code
    &client_id=psa_3f9c1b2a4d5e6f708192a3b4
    &redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback
    &scope=offers%3Aread%20orders%3Aread%20shipments%3Awrite
    &state=k3Jm9pQ2vX8sLw4T
    &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
    &code_challenge_method=S256
ParametrWymaganyZasady
response_typetakZawsze code.
client_idtakZ panelu aplikacji. Nieznany → 400 invalid_client w JSON, bez przekierowania.
redirect_uritakJeden z adresów zarejestrowanych w aplikacji, identyczny znak w znak. Niezarejestrowany → 400 w JSON, bez przekierowania.
scopetakZakresy rozdzielone spacją, wszystkie muszą być przyznane aplikacji. Puste → invalid_scope.
statetakMin. 8 znaków, losowy, wraca bez zmian. Porównaj po powrocie.
code_challengetak43 znaki base64url = SHA-256 z code_verifier.
code_challenge_methodtakTylko S256. Metoda plain nie jest przyjmowana.

Krok 3: powrót z kodem#

Zgoda
https://app.example.com/oauth/callback
    ?code=ps_ac_dGVzdG93eV9rb2RfYXV0b3J5emFjeWpueQ
    &state=k3Jm9pQ2vX8sLw4T
Odmowa
https://app.example.com/oauth/callback
    ?error=access_denied
    &state=k3Jm9pQ2vX8sLw4T

Krok 4: wymiana kodu na tokeny#

Żądanie
curl -s -X POST https://api.premiumsupply.pl/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=$CODE" \
  -d "redirect_uri=https://app.example.com/oauth/callback" \
  -d "code_verifier=$CODE_VERIFIER"
Odpowiedź 200
{
  "access_token": "ps_at_c2VsbGVyX3Rva2VuX2Rva3VtZW50YWNqYV8wOTg3NjU0MzIx",
  "token_type": "Bearer",
  "expires_in": 43200,
  "refresh_token": "ps_rt_cmVmcmVzaF90b2tlbl9kb2t1bWVudGFjamlfMTEyMjMzNDQ1NQ",
  "scope": "offers:read orders:read shipments:write"
}
  • Kod jest jednorazowy i żyje 10 minut. Drugie użycie, inny redirect_uri albo zły code_verifier400 invalid_grant.
  • Zakresy tokenu = zakresy, na które sprzedawca wyraził zgodę. Sprawdzisz je przez GET /v1/me.
  • Jeden sprzedawca może wyrazić zgodę wielokrotnie — każda daje niezależną parę tokenów.

Odświeżanie i rotacja refresh tokenu#

Żądanie
curl -s -X POST https://api.premiumsupply.pl/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=$REFRESH_TOKEN"
Odpowiedź 200 — NOWY refresh_token
{
  "access_token": "ps_at_bm93eV9hY2Nlc3NfdG9rZW5fcG9fcm90YWNqaV8wMDExMjIzMw",
  "token_type": "Bearer",
  "expires_in": 43200,
  "refresh_token": "ps_rt_bm93eV9yZWZyZXNoX3Rva2VuX3N0YXJ5X2p1el9uaWVfZHppYWxh",
  "scope": "offers:read orders:read shipments:write"
}
Pseudokod bezpiecznego odświeżenia
funkcja odśwież(sesja):
  zablokuj(sesja)                          # jedno odświeżenie naraz dla tej sesji
  odpowiedź = POST /oauth/token (grant_type=refresh_token, refresh_token=sesja.refresh)

  jeśli odpowiedź.status == 200:
    zapisz_atomowo(sesja, access = odpowiedź.access_token,
                          refresh = odpowiedź.refresh_token)   # NAJPIERW zapis, potem użycie
  w przeciwnym razie, gdy error == "invalid_grant":
    oznacz(sesja, "wymaga ponownej zgody")  # stary refresh już nie wróci
  odblokuj(sesja)

Access token wydany wcześniej pozostaje ważny do swojego wygaśnięcia — rotacja dotyczy tylko refresh tokenu. Zakresy nowej pary są identyczne z poprzednią; żeby dodać zakres, sprzedawca musi przejść zgodę ponownie.

Czasy życia#

CoWażnośćUwagi
Access token ps_at_12 h (expires_in: 43200)Ten sam czas w obu przepływach. Odnawiaj prewencyjnie, np. 5 minut przed końcem.
Refresh token ps_rt_90 dniJednorazowy — każde użycie wydaje nowy z nowym 90-dniowym terminem. Sprzedawca nieaktywny przez 90 dni musi wyrazić zgodę ponownie.
Kod autoryzacyjny ps_ac_10 minJednorazowy. Wymieniaj natychmiast po powrocie na redirect_uri.
client_secret pss_bez wygasaniaRotacja ręczna w panelu. Stary sekret gaśnie natychmiast; tokeny już wydane pozostają ważne.
Klucz idempotencji24 hNie jest tokenem, ale ma termin — po 24 h ta sama para (aplikacja, klucz) wykona żądanie ponownie.

Zakresy uprawnień#

Zakresy są krótkie i bez prefiksu. Aplikacja deklaruje je w panelu; token może mieć podzbiór. Sprzedawca na ekranie zgody widzi etykiety z kolumny „Co oznacza”. Kolumna „Zasoby w v1” pokazuje, co dziś wymaga danego zakresu — pozostałe zakresy są zarezerwowane dla zasobów zapowiedzianych w changelogu; proś o nie dopiero, gdy będą potrzebne.

ZakresCo oznacza (etykieta na ekranie zgody)Zasoby w v1
offers:readOdczyt Twoich ofert
  • GET /v1/offers
  • GET /v1/offers/{id}
offers:writeTworzenie i edycja ofert, zmiana cen i stanów
  • PATCH /v1/offers/{id}
orders:readOdczyt zamówień i danych kupujących
  • GET /v1/orders
  • GET /v1/orders/{id}
orders:writeZmiana statusów zamówieńzarezerwowany
shipments:readOdczyt przesyłekzarezerwowany
shipments:writeZgłaszanie wysyłek (kupujący dostaje powiadomienie)
  • POST /v1/orders/{id}/shipments
returns:readOdczyt zwrotów i reklamacjizarezerwowany
returns:writeObsługa zwrotów i reklamacjizarezerwowany
pricelists:readOdczyt cenników dostawzarezerwowany
pricelists:writeZarządzanie cennikami dostawzarezerwowany
policies:readOdczyt warunków zwrotów, reklamacji i gwarancjizarezerwowany
policies:writeZarządzanie warunkami zwrotów, reklamacji i gwarancjizarezerwowany
settings:readOdczyt ustawień sprzedażyzarezerwowany
settings:writeZmiana ustawień sprzedażyzarezerwowany
billing:readOdczyt salda i opłatzarezerwowany
messagingWiadomości z kupującymizarezerwowany
profile:readOdczyt danych Twojego kontazarezerwowany

GET /v1/me nie wymaga zakresu. Brak zakresu na zasobie → 403 insufficient_scope z nazwą brakującego zakresu w message.

Kiedy token traci ważność#

  • Upływ czasu — 12 h (access), 90 dni (refresh), 10 min (kod).
  • Użycie refresh tokenu — odwołuje ten konkretny refresh token (rotacja). Access tokeny wydane wcześniej działają do wygaśnięcia.
  • Usunięcie aplikacji w panelu — natychmiast odwołuje wszystkie jej tokeny u wszystkich sprzedawców.
  • Zawieszenie aplikacji (status suspended) — tokeny przestają być akceptowane, nowe nie są wydawane.
  • Rotacja client_secretnie odwołuje tokenów. To wymiana klucza, nie odcięcie integracji; na odcięcie jest usunięcie aplikacji.

Panel aplikacji (apps.developer.premiumsupply.pl) pokazuje aktywne tokeny per rodzaj z ostatnim użyciem, adresem IP i User-Agentem — to widok „kto ma dostęp do mojego sklepu”. Ekran zgody dla sprzedawców znajduje się pod https://premiumsupply.pl/oauth/consent i jest wywoływany wyłącznie przez /oauth/authorize.

Błędy OAuth#

Punkty /oauth/* zwracają błędy w kształcie RFC 6749: { error, error_description, traceId }. Kody invalid_client, invalid_grant, invalid_scope, invalid_request, unsupported_grant_type, access_denied i unsupported_response_type są opisane w katalogu błędów. Pozostałe zasoby (/v1/*) używają wspólnego kształtu { traceId, errors[] }.