# Save a card (token sale) Building with an AI agent? Start at https://docs.payme.io/agents.md - Sandbox: `POST https://sandbox.payme.io/api/generate-sale` - Production: `POST https://live.payme.io/api/generate-sale` ## Credentials - Request body field `seller_payme_id` (required): The seller's MPL. Send it in the JSON body: `generate-sale` doesn't read the `PayMe-Merchant-Key` header, so a request without a body MPL returns error `1`. Partners may send `seller_id` with `payme_client_key` instead. - Request body field `payme_client_key` (optional): Partners only: your partner key (or send the `PayMe-Partner-Key` header). With `seller_payme_id`, the seller must belong to you, otherwise error `20`. ## Overview Saves the buyer's card as a token (`buyer_key`) for later server-to-server charges. Send `sale_type: token` to save the card without charging (PayMe sets the price to `100`, which is not charged), or `capture_buyer: 1` on a normal sale to charge and save. The token arrives as `buyer_key` in the callback; you can also read it with [`get-buyer-key`](/payments/tokenization/post-get-buyer-key). `POST https://sandbox.payme.io/api/generate-sale` (production: `https://live.payme.io/api/generate-sale`), with a JSON body (`Content-Type: application/json`). | To | Send | |---|---| | Save the card without charging | `sale_type: token` | | Charge and save the card | `sale_type: sale` (default) and `capture_buyer: 1` | | Save a card on a page that can be opened only once | `sale_type: token`, `layout: token-capture` and `sale_should_be_opened_once: true` | | Charge a saved card later | `buyer_key` on a new `generate-sale`; see [Create a sale](/payments/hosted-payment-page/post-generate-sale) | To charge and save, send the same request as the example with `"sale_type": "sale"`, your real `sale_price` and `"capture_buyer": 1`. Rules: - Send `buyer_is_private: true` to tie the token to this seller. Without it the token is account-level: any seller of the same partner can charge it, and a `get-buyer-key` call that sends `seller_payme_id` fails with error `603`. - Token capture works with `credit-card`, `apple-pay`, `il-direct-debit` and `multi`; other methods return `4`. - `product_name` and a numeric `sale_price` are required even when nothing is charged. - Store `buyer_key` encrypted: it replaces the card number. ### Credentials Send `seller_payme_id` (the MPL) in the JSON body: this endpoint doesn't read the `PayMe-Merchant-Key` header, so a request without a body MPL returns error `1`. Partners add `payme_client_key` (or the `PayMe-Partner-Key` header) and may name the seller by `seller_id` instead. See [Authentication](/payments/getting-started/authentication). ### Callbacks When the sale is paid, PayMe sends a `POST` with an `application/x-www-form-urlencoded` body to `sale_callback_url`. The notification types and fields are on [Callbacks](/payments/getting-started/callbacks). ## Parameters This endpoint takes no path, query or header parameters. ## Request body Content type: `application/json` | Field | Type | Required | Description | | --- | --- | --- | --- | | `seller_payme_id` | string | yes | The seller's MPL. Send it in the JSON body: `generate-sale` doesn't read the `PayMe-Merchant-Key` header, so a request without a body MPL returns error `1`. Partners may send `seller_id` with `payme_client_key` instead. | | `payme_client_key` | string | no | Partners only: your partner key (or send the `PayMe-Partner-Key` header). With `seller_payme_id`, the seller must belong to you, otherwise error `20`. | | `seller_id` | string | no | Partners only: your own ID for the seller, set in `create-seller`, sent instead of `seller_payme_id`. Requires `payme_client_key`. | | `sale_type` | string | no | `token` – save the card without charging (the price is set to `100`). `sale` (default) – charge; add `capture_buyer: 1` to save the card too. Token capture works with `credit-card`, `apple-pay`, `il-direct-debit` and `multi` (other methods return `4`). The full list is on [Sale Types](/payments/codes-statuses/sale-types). | | `sale_price` | integer | yes | Amount in minor units. Required, but for a token sale any numeric value works: PayMe replaces it with `100` and charges nothing. A missing or non-numeric value returns `352`. For a charge-and-save sale, the default allowed range is `500`–`4000000`. | | `currency` | string | no | Sale currency, a 3-letter ISO 4217 code. Optional for a token sale (the account's default currency is used); required to charge. | | `product_name` | string | yes | Short description shown to the buyer and in the seller's records, up to 500 characters. Required, also for token sales (missing returns `353`). | | `capture_buyer` | string | no | `1` or `true` to save the buyer's card as a token after the payment; the token arrives as `buyer_key` in the callback. Omit it, or send `0`, to skip. Works with `credit-card`, `apple-pay`, `il-direct-debit` and `multi` (other methods return `4`). Can't be combined with `buyer_key` (`601`). | | `buyer_is_private` | boolean | no | With `capture_buyer` or a token sale: `true` ties the token to this seller, so `get-buyer-key` works with the seller's MPL. Without it the token is account-level: any seller of the same partner can charge it, and a `get-buyer-key` call that sends `seller_payme_id` fails with error `603`. | | `buyer_perform_validation` | boolean | no | With `capture_buyer` or a token sale: whether PayMe validates the card with the issuer before saving the token. Default `true`. Sending `true` without `capture_buyer` or a token sale returns `608`. | | `sale_payment_method` | string | no | Payment method; see [Available Payment Methods](/payments/wallets-apms). Default `credit-card`. Send `multi` to let the buyer choose among every method enabled on the account. Case-insensitive. An unknown value returns `24`; a method not enabled on the account returns `720` with the method name; `bacs` needs `GBP` and `sepa` needs `EUR` (`320`). Send a string: an array returns `368` (HTTP 400). | | `layout` | string | no | Payment page layout: `dynamic` (default), `dynamic-loose` (no social ID or CVV validation), `qr-sms` (Bit: QR code or SMS), `token-capture` (save a card without a charge; requires `sale_type: token`). Other values fall back to `dynamic`. | | `sale_should_be_opened_once` | boolean | no | `true`: with `layout: token-capture`, the page can be opened only once. Ignored otherwise. | | `buyer_key` | string | no | To charge a saved token later, on a new `generate-sale`: the call charges it at once, with no payment page. Can't be combined with `capture_buyer` (`601`) or `sale_type: token` (`602`). | | `transaction_id` | string | no | Your own order reference, up to 50 characters. PayMe returns it in responses and callbacks but doesn't check it for uniqueness. | | `market_fee` | number | no | The partner's (marketplace's) fee: a percentage of the sale price, VAT included, from `0` to `60`. It is added on top of PayMe's fees and transferred to the marketplace once a month. Default: the seller's market fee set at `create-seller`. A non-numeric value returns `10`, a negative one `304`, and more than 60 `308`. Ignored for sellers paid directly. | | `sale_callback_url` | string | no | Where PayMe sends server-to-server callbacks for this sale, as a form-encoded `POST`; see [Callbacks](/payments/getting-started/callbacks). If omitted, PayMe uses your partner's default callback URL, if one is set; a seller has no dashboard setting for it, so send it on every call. Must be a public address: `localhost`, private or loopback IP addresses and IPv6 addresses are refused with error `21`, which fails the whole call. | | `sale_return_url` | string | no | Where PayMe redirects the buyer (the iframe or the full page) after a successful payment, with the sale details as query parameters; see [Hosted Payment Page options](/accepting-payments/checkout-and-in-person/hosted-payment-page-options). If omitted, your partner's default return URL is used, if one is set. Same address rules as `sale_callback_url` (error `21`). | | `sale_send_notification` | boolean | no | `true`: PayMe sends the buyer a payment request with the payment link, by email to `sale_email` and by SMS to `sale_mobile`. Default `false`. | | `sale_email` | string | no | Buyer's email. Pre-fills the payment page and receives the payment request when `sale_send_notification` is `true`. | | `sale_mobile` | string | no | Buyer's mobile number. Pre-fills the payment page and receives the SMS payment request when `sale_send_notification` is `true` and the SMS service is active on the account. | | `sale_name` | string | no | Buyer's full name. Pre-fills the payment page (split at the first space into first and last name) and is used in notifications. | | `language` | string | no | Language of the payment page and the error messages. Default `he`. `he` and `en` are fully supported; `es`, `ru` and `ar` are accepted, but messages and the payment page are then mostly in English. Case-insensitive; any other value, or an empty string, returns error `51`. | ### Request example ```json { "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "sale_type": "token", "sale_price": 100, "currency": "ILS", "product_name": "Save card", "buyer_is_private": true, "sale_callback_url": "https://your-server.example.com/payme/callback", "language": "en" } ``` ## Code samples ### curl ```bash curl -X POST 'https://sandbox.payme.io/api/generate-sale' \ -H 'Content-Type: application/json' \ --data-raw '{ "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "sale_type": "token", "sale_price": 100, "currency": "ILS", "product_name": "Save card", "buyer_is_private": true, "sale_callback_url": "https://your-server.example.com/payme/callback", "language": "en" }' ``` ### Node.js ```javascript const response = await fetch("https://sandbox.payme.io/api/generate-sale", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "sale_type": "token", "sale_price": 100, "currency": "ILS", "product_name": "Save card", "buyer_is_private": true, "sale_callback_url": "https://your-server.example.com/payme/callback", "language": "en" }), }); console.log(await response.text()); ``` ### Python ```python import requests response = requests.post( "https://sandbox.payme.io/api/generate-sale", json={ "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "sale_type": "token", "sale_price": 100, "currency": "ILS", "product_name": "Save card", "buyer_is_private": True, "sale_callback_url": "https://your-server.example.com/payme/callback", "language": "en", }, ) print(response.text) ``` ### PHP ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => $body, ]); $response = curl_exec($ch); curl_close($ch); echo $response; ``` ## Responses ### 200 — Token sale created (success) | Field | Type | Description | | --- | --- | --- | | `status_code` | integer | `0` success, `1` error. | | `sale_url` | string | PayMe's payment page for this sale. Show it to the buyer in an iframe or a redirect, exactly as returned. It stays payable while the sale is `initial` or `failed`, until `sale_expiry_seconds` passes if you sent it. | | `payme_sale_id` | string | PayMe's sale ID. Store it: refunds, captures and queries use it. | | `payme_sale_code` | integer | Short numeric sale code, for display only. | | `price` | integer | Sale amount in minor units (`100` for a token sale). | | `transaction_id` | string | Your order reference, as sent. | | `currency` | string | Sale currency (ISO 4217). | | `sale_payment_method` | string | Payment method of the sale, for example `credit-card` or `multi`. | | `session` | string | Request ID. Quote it when you contact PayMe. | ```json { "status_code": 0, "session": "AtsJ0as5", "sale_url": "https://sandbox.payme.io/sale/generate/SALE1700-000000AA-EXAMPLE1-SALE0001", "payme_sale_id": "SALE1700-000000AA-EXAMPLE1-SALE0001", "payme_sale_code": 12345678, "price": 100, "transaction_id": null, "currency": "ILS", "sale_payment_method": "credit-card" } ``` ### 500 — Request refused. Most request errors return HTTP 500 with `status_code: 1`; some return 400, 403, 422 or 429. See [Errors](/payments/getting-started/errors). (error) | Field | Type | Description | | --- | --- | --- | | `status_code` | integer | `1` for an error. | | `status_error_code` | integer | PayMe's error code, for example `251` (seller not found) or `352` (invalid price). | | `status_error_details` | string | The error message, in the requested `language` (Hebrew by default). | | `status_additional_info` | string | Extra detail, usually the name of the field at fault. | | `session` | string | Request ID. Quote it when you contact PayMe. | ```json { "status_code": 1, "status_error_details": "Seller was not found", "status_additional_info": "seller_payme_id", "status_error_code": 251, "session": "AtsJ0as5" } ```