> ## 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

# Kody błędów

> Wszystkie błędy zwracane przez bramkę płatniczą, ich przyczyny — oraz mechanizmy, których API nie posiada.

## Kształt odpowiedzi błędu

Bramka zwraca błędy w jednym, stałym formacie:

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

| Pole      | Opis                                            |
| --------- | ----------------------------------------------- |
| `status`  | Kod statusu HTTP, powielony w treści odpowiedzi |
| `message` | Opis przeznaczony dla człowieka                 |

<Warning>
  **Nie opieraj logiki na treści `message`.** Rozgałęziaj wyłącznie po statusie HTTP. Komunikaty bywają zmieniane bez zapowiedzi, a część z nich zawiera wewnętrzne nazwy klas z backendu - na przykład `PaySubProductEntity not found`. Nie ma osobnego, maszynowego kodu błędu.
</Warning>

Dwa wyjątki od powyższego kształtu warto znać:

**Niepoprawny JSON w żądaniu** - błąd rozpoznawany, zanim dane trafią do logiki biznesowej:

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "status": 400,
  "message": "Malformed JSON request. Please check your request body."
}
```

**Nieoczekiwany błąd po stronie Paymove** - zawsze z tym samym, ogólnym komunikatem:

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "status": 500,
  "message": "Something went wrong"
}
```

## Tabela błędów

| Status | `message`                                                 | Przyczyna                                                                                        | Co zrobić                                                                                                                                                            |
| ------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `Malformed JSON request. Please check your request body.` | Body nie jest poprawnym JSON-em albo pole wyliczeniowe ma nieznaną wartość                       | Sprawdź składnię i typy pól                                                                                                                                          |
| `400`  | `Bank account not found for this partner`                 | Konto rozliczeniowe partnera nie jest skonfigurowane                                             | Skontaktuj się z Paymove                                                                                                                                             |
| `401`  | `Missing credentials`                                     | Brak nagłówka `X-API-KEY`                                                                        | Dodaj nagłówek                                                                                                                                                       |
| `401`  | `Invalid API key`                                         | Klucz nie został rozpoznany, wygasł albo został odwołany                                         | Sprawdź, czy używasz klucza właściwego dla środowiska: `sk_test_` w sandboxie, `sk_live_` na produkcji. Nowy klucz wygenerujesz w [Panelu](https://panel.paymove.io) |
| `403`  | `Product <uuid> does not belong to you`                   | `productId` należy do innego partnera                                                            | Sprawdź, czy `productId` pasuje do użytego klucza                                                                                                                    |
| `404`  | `PayProductEntity not found`                              | Produkt o podanym `productId` nie istnieje                                                       | Zweryfikuj `productId`                                                                                                                                               |
| `404`  | `WebhookEntity not found`                                 | Webhook o podanym `webhookId` nie istnieje                                                       | Zweryfikuj `webhookId`                                                                                                                                               |
| `404`  | `PaySubProductEntity not found`                           | Płatność o podanym identyfikatorze nie istnieje                                                  | Sprawdź, czy używasz właściwego identyfikatora                                                                                                                       |
| `500`  | `Something went wrong`                                    | Błąd wewnętrzny **albo brak `externalId` w żądaniu**, **albo niepoprawny format UUID w ścieżce** | Najpierw sprawdź kompletność body i poprawność `productId` - dopiero potem ponów wywołanie                                                                           |

## Czego w tym API nie ma

Ta sekcja jest równie ważna jak tabela powyżej. Poniższych mechanizmów bramka **nie posiada** - pisanie kodu, który je obsługuje, jest zbędne i wprowadza w błąd:

| Mechanizm                            | Stan                                                            |
| ------------------------------------ | --------------------------------------------------------------- |
| Limity liczby zapytań i status `429` | Nie istnieją - bramka nie ogranicza tempa wywołań               |
| Status `422`                         | Nie występuje. Błędy walidacji zwracane są jako `400` lub `500` |
| Nagłówek `Idempotency-Key`           | Nie istnieje. Rolę klucza idempotencji pełni `externalId`       |
| Wersjonowanie API                    | Brak. Nie ma prefiksu `/v1/` ani nagłówka wersji                |
| Nagłówki `Deprecation` / `Sunset`    | Nie są wysyłane                                                 |
| Maszynowy kod błędu (`code`, `type`) | Nie istnieje. Dostępne są wyłącznie `status` i `message`        |

## Błędy, które wyglądają jak sukces

Najgroźniejsza kategoria: API zwraca `200`, choć żądanie było błędne. Sprawdź te przypadki, zanim uznasz integrację za działającą.

<Warning>
  **Kwota z częścią dziesiętną jest po cichu obcinana.** `"price": 12.99` zostanie zapisane jako `12` grosze, a odpowiedź to `200`. Przeliczaj przez `Math.round(kwota * 100)`.
</Warning>

<Warning>
  **Nieznane pola są ignorowane.** Wysłanie `amount` zamiast `price`, `currency` czy `returnUrl` na najwyższym poziomie zamiast w `details` nie zgłosi błędu - płatność powstanie z niekompletnymi danymi.
</Warning>

<Warning>
  **Powtórzony `externalId` zwraca starą płatność.** Odpowiedź to `200` z `redirectUrl` utworzonym wcześniej, a nowa kwota jest odrzucana. Kwotę zmieniaj przez `PATCH /api/pay/product/{productId}/subproduct/{externalId}`.
</Warning>

<Warning>
  **Brak `externalId` kończy się kodem `500`, a nie `400`.** Zanim potraktujesz `500` jako błąd przejściowy i ponowisz wywołanie, sprawdź, czy Twoje body zawiera `externalId`.
</Warning>

<Warning>
  **Brak `price` albo `details.returnUrl` nie zgłasza żadnego błędu.** Dostajesz `200` i działający `redirectUrl` - do płatności bez kwoty albo bez powrotu do sklepu. Kompletność tych pól musisz sprawdzić po swojej stronie, przed wysłaniem żądania.
</Warning>

<Warning>
  **Niepoprawny format UUID w ścieżce też zwraca `500`.** Literówka w `productId` nie da czytelnego `400` - dostaniesz ogólne `Something went wrong`.
</Warning>

## Obsługa błędów w kodzie

```javascript theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
const response = await fetch(url, options);

if (!response.ok) {
  const error = await response.json(); // { status, message }

  switch (response.status) {
    case 401:
    case 403:
      // Problem z konfiguracją - ponawianie nie pomoże
      throw new PaymentConfigError(error.message);
    case 404:
      throw new PaymentNotFoundError(error.message);
    case 400:
      // Błędne żądanie - napraw dane, nie ponawiaj
      throw new PaymentRequestError(error.message);
    case 500:
      // Najpierw zweryfikuj kompletność body, dopiero potem ponów
      throw new PaymentServerError(error.message);
    default:
      throw new Error(`Paymove ${response.status}: ${error.message}`);
  }
}
```

<Info>
  Ponawianie ma sens wyłącznie przy `500` i błędach sieciowych, i tylko po upewnieniu się, że żądanie było kompletne. Kody `400`, `401`, `403` i `404` oznaczają problem po Twojej stronie - ponowienie zwróci ten sam wynik.
</Info>

## Błędy SDK

[SDK dla Node.js](/sdk/javascript) opakowuje powyższe odpowiedzi w typowane wyjątki:

| Klasa                    | Kiedy                                                                                               |
| ------------------------ | --------------------------------------------------------------------------------------------------- |
| `PaymoveValidationError` | Argument odrzucony przez SDK jeszcze przed wysłaniem żądania. Pole `field` wskazuje parametr        |
| `PaymoveApiError`        | API zwróciło status inny niż 2xx. Pola `statusCode` i `responseBody` zawierają oryginalną odpowiedź |
| `PaymoveNetworkError`    | Żądanie nie doszło do skutku. Pole `cause` zawiera pierwotny wyjątek                                |

<Warning>
  Walidacja `amount` w SDK sprawdza wyłącznie, czy wartość jest liczbą większą od zera. Kwota `49.99` przejdzie tę kontrolę, a API obetnie ją do 49 groszy.
</Warning>

## Co dalej

<CardGroup cols={2}>
  <Card title="REST API" icon="code" href="/rest-api">
    Poprawna struktura żądania i pełne odpowiedzi.
  </Card>

  <Card title="Weryfikacja podpisu" icon="shield-check" href="/webhook-signature">
    Obsługa błędów po stronie odbioru webhooka.
  </Card>
</CardGroup>
