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

> Full DocPay endpoint reference: product, subproducts and QR codes, pricing, forms, UI customization, webhooks.

PAY API (DocPay) endpoint reference: product, subproduct and QR codes, pricing, forms, UI customization and webhooks. The basic integration scenario (product + subproduct) is described in the [Tutorial](/en/products/docpay/tutorial).

***

## 1. Product

The product represents your service in the Paymove system and is the root of the whole model - subproducts, pricing, forms, customizations, and webhooks are attached to a specific product. You create the product **once**.

### Endpoints – Product

| Method | Endpoint                       | Description        |
| ------ | ------------------------------ | ------------------ |
| POST   | `/api/product/pay`             | Creates a product. |
| PATCH  | `/api/product/pay/{productId}` | Updates a product. |

### Create product

```
POST /api/product/pay
```

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "partnerId": "78562c79-2f5c-4415-8af4-c871eea92ef2",
  "productType": "PAY",
  "name": "Test Product",
  "shortName": "3456",
  "location": "Warsaw",
  "timezone": "Europe/Warsaw"
}
```

| Field         | Description                               |
| ------------- | ----------------------------------------- |
| `partnerId`   | Partner identifier (assigned by Paymove). |
| `productType` | Product type - for DocPay: `PAY`.         |
| `name`        | Product name.                             |
| `shortName`   | Short display name.                       |
| `location`    | Location (e.g. city).                     |
| `timezone`    | IANA timezone (e.g. `Europe/Warsaw`).     |

**Response (200):**

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "id": "d0a834f9-94a4-4b3a-aa10-27d911e3633f",
  "name": "Test Product",
  "location": "Warsaw",
  "partner": {
    "id": "78562c79-2f5c-4415-8af4-c871eea92ef2",
    "name": "Partner Name Sp. z o.o.",
    "productTypes": ["PAY"]
  },
  "timezone": "Europe/Warsaw",
  "shortName": "3456",
  "emailEnabled": true,
  "smsEnabled": false,
  "fee": {
    "id": "01f9754e-78e3-4b2c-8186-d6862598a95a",
    "minimum": 30,
    "amount": 5,
    "fixed": false
  },
  "productType": "PAY",
  "status": "ACTIVE",
  "creator": "PAYMOVE",
  "reviewEnabled": false,
  "createdAt": 1783245692.623763835,
  "updatedAt": 1783245692.623763835
}
```

The `id` field is the product identifier (`productId`) used in the other endpoints.

### Update product

```
PATCH /api/product/pay/{productId}
```

You can send only the fields to change; other relations (pricing, forms, webhooks) remain unchanged.

***

## 2. Subproduct and QR codes

A subproduct represents a single payment (e.g. payment request, ticket) linked to an external identifier (`externalId`). The response contains a QR code generated in the chosen format (PNG/SVG). After scanning, the user is taken to payment for that subproduct.

### Input fields

| Field         | Description                                                                                   |
| ------------- | --------------------------------------------------------------------------------------------- |
| `externalId`  | Document identifier in your system. Displayed in the Paymove panel as the document number.    |
| `imageFormat` | QR code image format: `svg` or `png`.                                                         |
| `bannerType`  | Banner/card type (e.g. `ticket`) - affects presentation/layout.                               |
| `price`       | Amount in minor units (e.g. `25000` = 250.00 PLN).                                            |
| `details`     | Key-value pairs displayed to the customer on the payment page (e.g. `"Document no.": "..."`). |

### Endpoint – Subproduct

| Method | Endpoint                                  | Description                                                                                                                               |
| ------ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| POST   | `/api/pay/product/{productId}/subproduct` | Registers subproduct by `externalId` and generates QR code. After registration, the user can pay for the subproduct by scanning the code. |

### Register subproduct

```
POST /api/pay/product/{productId}/subproduct
```

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "externalId": "test-external#2",
  "imageFormat": "svg",
  "bannerType": "ticket",
  "price": 25000,
  "details": {
    "Document no.": "test-external#2"
  }
}
```

The response (200) is the binary image content with the QR code in the format set by `imageFormat` (`Content-Type: application/octet-stream`) - save the response body directly to a file. The image is a print-ready banner with a QR code redirecting to payment for this subproduct.

***

## 3. Pricing

Pricing defines payment options available to the customer - e.g. "1 hour", "full day", "weekend ticket". Each entry is a separate purchase option shown on the product page.

### Pricing entry fields

| Field         | Description                                         |
| ------------- | --------------------------------------------------- |
| `price`       | Amount in minor units (e.g. 150 = 1.50 PLN).        |
| `description` | Human-readable description (e.g. "1 hour parking"). |
| `details`     | JSON with extra data (e.g. `currency`, `vat`).      |

### Endpoints – Pricing

| Method | Endpoint                                                  | Description                                                      |
| ------ | --------------------------------------------------------- | ---------------------------------------------------------------- |
| GET    | `/api/pay/products/{productId}/pricing/entries`           | Returns all pricing entries for the product.                     |
| POST   | `/api/pay/products/{productId}/pricing/entries`           | Creates a new pricing entry.                                     |
| PATCH  | `/api/pay/products/{productId}/pricing/entries/{entryId}` | Updates a pricing entry.                                         |
| DELETE | `/api/pay/products/{productId}/pricing/entries/{entryId}` | Deletes a pricing entry. Option no longer available at checkout. |

### Create pricing entry

```
POST /api/pay/products/{productId}/pricing/entries
```

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "price": 150,
  "description": "1 hour parking",
  "details": "{\"currency\":\"PLN\",\"vat\":23}"
}
```

### Update pricing entry

```
PATCH /api/pay/products/{productId}/pricing/entries/{entryId}
```

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "price": 150000000,
  "details": "{\"currency\":\"PLN\",\"vat\":23}"
}
```

Changes are immediately visible to customers selecting that option.

***

## 4. Forms

Forms collect data required for purchase - e.g. email, license plate, contact details. Forms are versioned; each change (add/edit/remove field) creates a new version. The current version is marked with `isCurrent`.

### Form field (Field) properties

| Field        | Description                                           |
| ------------ | ----------------------------------------------------- |
| `name`       | Field name (e.g. `email`).                            |
| `type`       | Type: `TEXT`, `NUMBER`, `SELECT`, `EMAIL`, etc.       |
| `labels`     | Language labels (e.g. `pl`, `en`).                    |
| `validators` | Validations: `required`, `pattern`, `maxLength`, etc. |

### Endpoints – Forms

| Method | Endpoint                                     | Description                                                    |
| ------ | -------------------------------------------- | -------------------------------------------------------------- |
| GET    | `/api/pay/product/{productId}/form`          | List forms for PAY products.                                   |
| GET    | `/api/pay/product/{productId}/form/{formId}` | Form details (fields, version, isCurrent).                     |
| POST   | `/api/pay/product/{productId}/form`          | Creates a form for the product.                                |
| DELETE | `/api/pay/product/{productId}/form/{formId}` | Deletes a form. Does not affect data from completed purchases. |

### Endpoints – Fields

| Method | Endpoint                                                     | Description                                                             |
| ------ | ------------------------------------------------------------ | ----------------------------------------------------------------------- |
| GET    | `/api/pay/product/{productId}/form/field/{fieldId}`          | Field details.                                                          |
| POST   | `/api/pay/product/{productId}/form/{formId}/fields`          | Adds a field - creates a new form version; new version becomes current. |
| PATCH  | `/api/pay/product/{productId}/form/{formId}/field/{fieldId}` | Updates a field - creates a new form version with updated field.        |
| DELETE | `/api/pay/product/{productId}/form/{formId}/field/{fieldId}` | Removes a field - creates a new version without that field.             |

### Create form

```
POST /api/pay/product/{productId}/form
```

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "fields": [],
  "version": 1,
  "isCurrent": true
}
```

### Add field (e.g. email)

```
POST /api/pay/product/{productId}/form/{formId}/fields
```

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "name": "email",
  "type": "TEXT",
  "labels": {
    "en": "Email",
    "pl": "Adres e-mail"
  },
  "validators": {
    "required": true,
    "maxLength": 100,
    "pattern": "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$"
  }
}
```

This automatically creates a new form version; the previous one is no longer current (`isCurrent`).

***

## 5. UI customization

Customization controls the look and copy of the product checkout page. All text (titles, buttons, descriptions) is configured via API - the frontend is fully driven by the partner without code changes.

### Customization parameters - what they modify

| Parameter           | What it modifies                                     | Where it appears                                                                                |
| ------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `title`             | Main page/product title.                             | Header at the top of the checkout view.                                                         |
| `subtitle`          | Text below the title.                                | Directly under the title (subtitle).                                                            |
| `buttonText`        | Call-to-action button label (e.g. "Buy now", "Pay"). | Confirmation button on the payment page.                                                        |
| `summaryHeader`     | Summary section heading.                             | Heading of the order summary block before payment.                                              |
| `summaryFirstLine`  | First line in the summary section.                   | Text in the summary (e.g. amount or product description).                                       |
| `summarySecondLine` | Second line in the summary section.                  | Next line in the summary.                                                                       |
| `cardDescription`   | Product card description.                            | Description on the product card/listing (e.g. product picker or preview).                       |
| `locale`            | Language/locale (e.g. `pl-PL`, `en-GB`).             | Determines which text set is used; allows multiple customizations per product (e.g. PL and EN). |

You can have multiple customizations per product with different `locale`; the system picks the right one by user or context.

### Endpoints – Customization

| Method | Endpoint                                          | Description                                                                                             |
| ------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| GET    | `/api/pay/product/{productId}/customization`      | List all customizations.                                                                                |
| GET    | `/api/pay/product/{productId}/customization/{id}` | Customization details (all fields).                                                                     |
| POST   | `/api/pay/product/{productId}/customization`      | Create customization for product.                                                                       |
| PATCH  | `/api/pay/product/{productId}/customization/{id}` | Update customization (any fields). Change is reflected immediately on the frontend.                     |
| DELETE | `/api/pay/product/{productId}/customization/{id}` | Delete customization. Product stops using that config; other customizations and services are unchanged. |

### Create customization

```
POST /api/pay/product/{productId}/customization
```

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "title": "New product",
  "subtitle": "Subtitle",
  "summaryHeader": "Summary header",
  "summaryFirstLine": "First line",
  "summarySecondLine": "Second line",
  "buttonText": "Buy now",
  "cardDescription": "Card description",
  "locale": "pl-PL"
}
```

### Update customization

```
PATCH /api/pay/product/{productId}/customization/{id}
```

You can send only the fields you want to change, e.g.:

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "summaryFirstLine": "First line",
  "summarySecondLine": "Second line",
  "buttonText": "Buy now",
  "cardDescription": "Card description",
  "locale": "pl-PL"
}
```

Changes are immediately reflected on the product checkout page.

***

## 6. Webhooks

Webhooks automatically notify the partner of completed payments or fetch pricing from an external system. After registering a webhook, assign it to a product - then events for that product are sent to your endpoint.

### Webhook configuration fields

| Field              | Description                                                           |
| ------------------ | --------------------------------------------------------------------- |
| `endpoint`         | URL Paymove sends the request to (POST).                              |
| `method`           | HTTP method (e.g. `POST`).                                            |
| `headers`          | Headers sent with the request (e.g. `Authorization`, `Content-Type`). |
| `requestTemplate`  | Request body template (variables substituted by Paymove).             |
| `responseTemplate` | Expected response structure from the partner.                         |
| `expectedCode`     | Expected HTTP response code (e.g. 200).                               |
| `expectedResponse` | Expected response body (e.g. `{ "status": "ok" }`).                   |
| `retries`          | Number of retries on failure.                                         |
| `type`             | Event type (e.g. `PAYMENT`).                                          |

### Endpoints – Webhooks

| Method | Endpoint                                                   | Description                                              |
| ------ | ---------------------------------------------------------- | -------------------------------------------------------- |
| GET    | `/api/pay/plugin/webhook`                                  | List webhooks. Optional query: `productId`, `partnerId`. |
| GET    | `/api/pay/plugin/webhook/{webhookId}`                      | Webhook details.                                         |
| GET    | `/api/pay/plugin/webhook/{webhookId}/products`             | Products linked to the webhook.                          |
| POST   | `/api/pay/plugin/webhook`                                  | Create webhook.                                          |
| POST   | `/api/pay/plugin/webhook/{webhookId}/products/{productId}` | Assign webhook to product.                               |
| PATCH  | `/api/pay/plugin/webhook/{webhookId}`                      | Update webhook. Affects all linked products.             |

### Create webhook

```
POST /api/pay/plugin/webhook
```

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "name": "PaymentSuccessHook",
  "endpoint": "https://example.com/webhooks/payment-success",
  "method": "POST",
  "requestTemplate": {
    "productId": "productId",
    "event": "event"
  },
  "responseTemplate": {
    "status": "ok"
  },
  "expectedCode": 200,
  "expectedResponse": "{ \"status\": \"ok\" }",
  "retries": 3,
  "partnerId": "6909ca83-410f-47c4-910d-2057f8565a8c",
  "type": "PAYMENT",
  "headers": {
    "Authorization": ["Bearer abc123"],
    "Content-Type": ["application/json"]
  }
}
```

**Response (200):** the webhook object with an assigned `id` (webhook identifier) and a `signingSecret` field:

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "id": "18e19688-bdda-4843-8777-0f04d0143c77",
  "name": "PaymentSuccessHook",
  "endpoint": "https://example.com/webhooks/payment-success",
  "method": "POST",
  "requestTemplate": { "productId": "productId", "event": "event" },
  "responseTemplate": { "status": "ok" },
  "expectedCode": 200,
  "expectedResponse": "{ \"status\": \"ok\" }",
  "retries": 3,
  "type": "PAYMENT",
  "headers": {
    "Authorization": ["Bearer abc123"],
    "Content-Type": ["application/json"]
  },
  "signingSecret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
```

<Warning>
  The `signingSecret` is used to verify the signature of requests coming from Paymove. Store it securely.
</Warning>

After creating, call **POST** `/api/pay/plugin/webhook/{webhookId}/products/{productId}` to link the webhook to the product (response: the webhook object). From then on, events (e.g. successful payment) for that product are sent to your `endpoint`.
