> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paymove.io/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> ## paymove integration rules for AI agents
>
> These rules are verified against the live API. Follow them exactly.
>
> 1. Amounts are INTEGERS in grosze (minor units). `1000` means 10.00 PLN. PLN is the only supported currency and there is no currency field in the API.
> 2. A float `price` (e.g. `12.99`) is SILENTLY TRUNCATED to 12 grosze and still returns HTTP 200. Always send an integer: `Math.round(pln * 100)`.
> 3. Authenticate with the `X-API-KEY` header. Never `Authorization: Bearer`. Sandbox keys start with `sk_test_`, production keys with `sk_live_`.
> 4. Server-side only. A call from a merchant page is rejected. Never put the key in frontend code.
> 5. Unknown request fields are silently ignored and still return HTTP 200 — a wrong body shape looks like success. Match the documented shape exactly.
> 6. A missing `externalId` returns HTTP 500 `Something went wrong`, not 400. A missing `price` or `details.returnUrl` returns HTTP 200 and a usable `redirectUrl` — no error at all. Validate the body yourself before sending it.
> 7. Always verify the `X-Paymove-Signature` header on incoming webhooks before trusting them: https://docs.paymove.io/en/webhook-signature.md
> 8. Errors are `{"status": <int>, "message": "<text>"}`. Branch on the HTTP status only — never on `message`, which is unstable and leaks internal class names.
> 9. There is no rate limiting, no HTTP 429, no HTTP 422, no `Idempotency-Key` header and no API versioning. Do not write code that handles them.
> 10. Re-POSTing an `externalId` that already exists returns HTTP 200 with the ORIGINAL `redirectUrl` and silently discards EVERY field you send — the new price, description and details are all ignored. Use `PATCH /api/pay/product/{productId}/subproduct/{externalId}` to change a price.
> 11. Webhooks fire only on `COMPLETED` by default, and `retries` defaults to `0` (no retries) unless you set it explicitly. Delivery counts as successful when the HTTP status equals `expectedCode` — the response body is never inspected.
> 12. Never fulfil an order on the `returnUrl` redirect. The checkout does not redirect there by itself: the customer has to click "back to shop", and inside the widget modal that redirect never happens. Fulfil only in the webhook handler, after verifying the signature.
> 13. Do not pin a version of `@paymove-io/sdk` — install the latest.
> 14. Full documentation index: https://docs.paymove.io/llms.txt · Copy-paste quickstart: https://docs.paymove.io/en/quickstart.md · Agent skill: https://docs.paymove.io/skill.md

# REST API

> Tworzenie płatności w bramce przez zwykłe wywołanie HTTP — pełny request, nagłówki i odpowiedź.

REST API pozwala tworzyć płatności w ramach głównego produktu. Każde wywołanie tworzy płatność, którą klient może opłacić poprzez otrzymany `redirectUrl`.

<Warning>
  Przed rozpoczęciem integracji przez REST API upewnij się, że masz skonfigurowany produkt i webhook. Przejdź do [Konfiguracja](/webhooks), aby wykonać wymagane kroki.
</Warning>

<Warning>
  Wywołuj API wyłącznie po stronie serwera. Żądanie wysłane ze strony Twojego sklepu zostanie odrzucone - a klucz i tak nigdy nie może trafić do kodu frontendowego.
</Warning>

## 1. Endpoint

```
POST https://gateway-api.sandbox.paymove.io/api/pay/product/{productId}/subproduct/pricing
```

| Środowisko | Adres bazowy                             |
| ---------- | ---------------------------------------- |
| Sandbox    | `https://gateway-api.sandbox.paymove.io` |
| Produkcja  | `https://api.paymove.io`                 |

### Nagłówki

| Nagłówek       | Wartość                                                            |
| -------------- | ------------------------------------------------------------------ |
| `Content-Type` | `application/json`                                                 |
| `X-API-KEY`    | Twój klucz API - `sk_test_…` w sandboxie, `sk_live_…` na produkcji |

## 2. Przykładowe wywołanie

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
curl --request POST \
  --url https://gateway-api.sandbox.paymove.io/api/pay/product/891412c8-8717-4449-9543-e34112bec470/subproduct/pricing \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  --data '{
    "price": 1000,
    "externalId": "order-123",
    "details": {
      "returnUrl": "https://merchant-shop.com/payment/success",
      "productName": "Koszulka sportowa",
      "email": "klient@example.com",
      "locale": "pl-PL",
      "triggerPayment": "BLIK",
      "qrStepEnabled": true
    }
  }'
```

To samo w Node.js:

```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
const url = `https://gateway-api.sandbox.paymove.io/api/pay/product/${productId}/subproduct/pricing`;

const response = await fetch(url, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-KEY": process.env.PAYMOVE_API_KEY,
  },
  body: JSON.stringify({
    price: Math.round(10.0 * 100), // zawsze liczba całkowita w groszach
    externalId: "order-123",
    details: {
      returnUrl: "https://merchant-shop.com/payment/success",
      productName: "Koszulka sportowa",
      email: "klient@example.com",
      locale: "pl-PL",
      triggerPayment: "BLIK",
      qrStepEnabled: true,
    },
  }),
});

if (!response.ok) {
  const error = await response.json(); // { status, message }
  throw new Error(`Paymove ${response.status}: ${error.message}`);
}

const data = await response.json();
console.log(data.redirectUrl);
```

### Parametry body

| Pole                     | Typ       | Wymagane | Opis                                                                                                                                                                                                                                      |
| ------------------------ | --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `price`                  | `integer` | Tak      | Kwota w groszach jako **liczba całkowita** (`1000` = 10,00 PLN)                                                                                                                                                                           |
| `externalId`             | `string`  | Tak      | Unikalny identyfikator zamówienia po Twojej stronie                                                                                                                                                                                       |
| `details.returnUrl`      | `string`  | Tak      | URL powrotu klienta po płatności                                                                                                                                                                                                          |
| `details.productName`    | `string`  | Nie      | Nazwa produktu widoczna na checkoucie                                                                                                                                                                                                     |
| `details.email`          | `string`  | Nie      | Adres e-mail klienta, uzupełniany na checkoucie                                                                                                                                                                                           |
| `details.locale`         | `string`  | Nie      | Nadpisuje locale z głównego produktu (np. `pl-PL`)                                                                                                                                                                                        |
| `details.recipient`      | `string`  | Nie      | Nazwa odbiorcy wyświetlana na checkoucie                                                                                                                                                                                                  |
| `details.triggerPayment` | `enum`    | Nie      | Metoda płatności wybrana z góry w checkoucie (Google Pay i Apple Pay startują automatycznie)                                                                                                                                              |
| `details.qrStepEnabled`  | `boolean` | Nie      | Kod QR na desktopie -  klient kończy płatność na telefonie                                                                                                                                                                                |
| `details.autoclose`      | `integer` | Nie      | Po ilu ms od udanej płatności checkout sam wraca na `details.returnUrl`. Odliczanie widać w przycisku „Wróć do sklepu", a interakcja klienta je anuluje. W widgecie zamiast przekierowania zamyka się modal. Brak pola = bez auto-powrotu |

<Warning>
  **`price` musi być liczbą całkowitą.** Wartość z częścią dziesiętną (np. `12.99`) zostanie po cichu obcięta do `12` groszy, a API i tak zwróci `200`. Przeliczaj złotówki przez `Math.round(kwota * 100)`.
</Warning>

<Info>
  Bramka rozlicza wyłącznie w **PLN** - w żądaniu nie ma pola waluty. Pole `currency`, jeśli je wyślesz, zostanie zignorowane.
</Info>

<Info>
  `details` to swobodny obiekt - możesz przekazać w nim własne pola, a Paymove je zachowa. Checkout odczytuje jednak tylko: `returnUrl`, `redirectUrl`, `productName`, `email`, `locale`, `orderId`, `recipient`, `triggerPayment`, `qrStepEnabled` i `autoclose`. Pozostałe pola (np. `customerId`) są wyłącznie przekazywane na wylot i nigdzie się nie wyświetlają.
</Info>

<Warning>
  Nieznane pola w body są **po cichu ignorowane**, a API zwraca `200`. Wysłanie `amount` zamiast `price` albo `returnUrl` na najwyższym poziomie zamiast w `details` nie zgłosi błędu - płatność powstanie z niekompletnymi danymi. Trzymaj się dokładnie powyższej struktury.
</Warning>

### Odpowiedź

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "redirectUrl": "https://checkout.sandbox.paymove.io/891412c8-8717-4449-9543-e34112bec470?externalId=ec6RtwTZKb"
}
```

| Pole          | Typ      | Opis                                                                              |
| ------------- | -------- | --------------------------------------------------------------------------------- |
| `redirectUrl` | `string` | Adres checkoutu -  przekieruj klienta pod ten URL w celu sfinalizowania płatności |

Odpowiedź ma status HTTP `200` (nie `201`).

<Info>
  Parametr `externalId` w zwróconym adresie (tutaj `ec6RtwTZKb`) to **wygenerowany przez Paymove 10-znakowy skrót płatności**, a nie Twój `externalId` (`order-123`). Zapisz go u siebie - posługujesz się nim przy [sprawdzaniu statusu płatności](/payment-status).
</Info>

### Błędy

Wszystkie błędy mają kształt `{"status": <int>, "message": "<tekst>"}`. Rozgałęziaj logikę **po statusie HTTP**, nigdy po treści `message`.

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "status": 401,
  "message": "Invalid API key"
}
```

Pełna lista: [Kody błędów](/errors).

### Wskazanie metody płatności

Pole `details.triggerPayment` z góry wybiera metodę płatności w checkoucie, dzięki czemu klient nie musi jej szukać na liście.

| Wartość       | Metoda             |
| ------------- | ------------------ |
| `GPAY`        | Google Pay         |
| `APAY`        | Apple Pay          |
| `BLIK`        | BLIK               |
| `CARD`        | Płatność kartą     |
| `TRANSFER`    | Przelew tradycyjny |
| `PAY_BY_LINK` | Pay by Link        |
| `PAYPO`       | PayPo              |

Nieznana wartość jest pomijana -  checkout zachowa się wtedy standardowo.

<Warning>
  Automatyczny start - czyli otwarcie płatności tak, jakby klient kliknął **Zapłać** - dotyczy **wyłącznie Google Pay i Apple Pay**. Przy pozostałych pięciu metodach kafelek jest tylko zaznaczony, a klient klika **Zapłać** sam. Nie buduj procesu, który zakłada, że BLIK czy PayPo wystartują same.
</Warning>

<Info>
  Nawet dla portfeli auto-start wymaga, żeby checkout znał adres e-mail klienta -  przekaż go w `details.email`. Nie zadziała też w [widgecie osadzonym w modalu](/sdk/widget) ani gdy metoda nie jest dostępna dla Twojego produktu. W każdym z tych przypadków metoda zostaje jedynie zaznaczona.
</Info>

Auto-start działa wyłącznie przy pierwszym wyświetleniu checkoutu. Gdy klient sam wybierze metodę płatności, nie uruchomi się ponownie -  aż do odświeżenia strony.

<Warning>
  Google Pay i Apple Pay otwierają natywne okno przeglądarki, które zwykle wymaga gestu klienta -  automatyczne uruchomienie może zostać przez nią zablokowane. Klient zobaczy wtedy standardowy ekran płatności i opłaci zamówienie ręcznie.
</Warning>

### Krok z kodem QR

Pole `details.qrStepEnabled` włącza dodatkowy ekran startowy checkoutu na desktopie: zamiast formularza płatności klient widzi kod QR, skanuje go telefonem i kończy płatność na nim. Przydaje się przy BLIK-u i portfelach dostępnych wyłącznie na telefonie.

Krok jest opcjonalny -  pominięcie pola albo `false` oznacza, że checkout od razu pokazuje formularz płatności.

<Info>
  Kod QR pojawia się wyłącznie na desktopie. Na telefonie oraz przy ustawionym `details.triggerPayment` checkout pomija ten krok niezależnie od wartości pola.
</Info>

## 3. Przekierowanie klienta

```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
window.location.href = data.redirectUrl;
```

Po zakończonej płatności checkout pokazuje ekran potwierdzenia z przyciskiem „Wróć do sklepu". Dopiero kliknięcie tego przycisku przenosi klienta pod adres podany w `details.returnUrl` - do URL-a nie są doklejane żadne parametry.

<Warning>
  Powrót na `returnUrl` **nie jest potwierdzeniem płatności** - i wcale nie musi nastąpić. Klient, który zamknie kartę po zapłaceniu, nigdy nie trafi na Twój adres, a sam `returnUrl` to zwykły publiczny URL, który można otworzyć bez płacenia. Zamówienie realizuj wyłącznie po otrzymaniu i [zweryfikowaniu webhooka](/webhook-signature).
</Warning>

## 4. Powtórne użycie `externalId`

`externalId` jest kluczem płatności po stronie Paymove. Ponowne wysłanie żądania z tym samym `externalId` **nie tworzy nowej płatności ani nie zwraca błędu** - API odpowiada `200` i zwraca `redirectUrl` płatności utworzonej wcześniej.

<Warning>
  Przy powtórzeniu **nowa wartość `price` i pozostałe pola są po cichu odrzucane**. Jeśli ponowisz żądanie ze skorygowaną kwotą, obowiązywać będzie kwota pierwotna, a odpowiedź niczym tego nie zasygnalizuje. Dla każdego zamówienia używaj nowego, unikalnego `externalId`.
</Warning>

Aby zmienić kwotę istniejącej płatności, użyj osobnego wywołania:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
curl --request PATCH \
  --url https://gateway-api.sandbox.paymove.io/api/pay/product/891412c8-8717-4449-9543-e34112bec470/subproduct/order-123 \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  --data '{ "price": 1500 }'
```

Wywołanie aktualizuje wyłącznie `price` - pozostałe pola są pomijane.

## 5. Co dalej

<CardGroup cols={2}>
  <Card title="Weryfikacja podpisu webhooka" icon="shield-check" href="/webhook-signature">
    Obowiązkowy krok przed realizacją zamówienia.
  </Card>

  <Card title="Statusy płatności" icon="list-check" href="/payment-status">
    Statusy, payload webhooka i sprawdzanie stanu płatności.
  </Card>

  <Card title="Kody błędów" icon="triangle-exclamation" href="/errors">
    Pełna lista błędów API i sposoby ich obsługi.
  </Card>

  <Card title="SDK JavaScript" icon="cube" href="/sdk/javascript">
    Gotowy klient dla Node.js.
  </Card>
</CardGroup>
