Recurring and saved methods
Saving a method and charging it again needs a rail that both declares mandate support and
implements it. No rail enabled today does, and the shape of that matters when you build:
setup_future_usage, an explicit mandate_data block and a zero-amount setup all complete with
200 succeeded and return mandate_id: null. Nothing is stored, and the later off-session
charge is refused with ERR_4088. Treat a missing mandate_id on the first payment as the
signal, not the status of that payment.
Read this page to design for it, and build it once support confirms a mandate-capable rail is enabled for you. There is no API flag to check: whether a rail carries mandates is a property of the rail, so ask support.
Subscriptions are a separate question. The /subscriptions endpoints are billed by a billing
processor — a connector class separate from the rails that move the money — so they are not
unlocked by a mandate-capable rail, or by a card one. See
Subscriptions in the API
reference.
Save a payment method once, then charge it again without the customer re-entering anything. This powers two flows over the same API:
- One-click — a returning customer pays with a method they already saved.
- Off-session (recurring) — you charge a saved method later with no customer present, for subscriptions, top-ups, or usage billing.
Both work by saving a method against a customer_id on the first payment, then
referencing the token TensorRail returns on later payments. You never handle or store
card data — the token is a TensorRail reference, not the underlying credential.
Saving and reuse are available for methods that support it (such as cards). Methods that are inherently single-use in a market are charged fresh each time; the API shape is the same, so you can write one integration and let the platform apply what each method supports.
1. Save a method on the first payment
Set setup_future_usage and attach a customer_id. Use off_session when you intend to
charge later with no customer present (subscriptions), or on_session when the customer
will return and confirm themselves (one-click).
setup_future_usage saves the method used by this payment, so a confirming call still has
to say how to pay. Omitting the payment method with confirm: true is refused with
ERR_2002 — A payment token or payment method data or ctp service details is required.
- curl
- Node
- Python
curl -X POST https://api.tensorrail.com/payments \
-H "api-key: rail_full_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"amount": 50000,
"currency": "INR",
"customer_id": "cus_8x2Kp",
"setup_future_usage": "off_session",
"confirm": true,
"payment_method": "upi",
"payment_method_type": "upi_collect",
"payment_method_data": { "upi": { "upi_collect": { "vpa_id": "success@upi" } } },
"return_url": "https://yourshop.com/checkout/complete"
}'
const res = await fetch("https://api.tensorrail.com/payments", {
method: "POST",
headers: { "api-key": "rail_full_test_xxx", "Content-Type": "application/json" },
body: JSON.stringify({
amount: 50000,
currency: "INR",
customer_id: "cus_8x2Kp",
setup_future_usage: "off_session",
confirm: true,
payment_method: "upi",
payment_method_type: "upi_collect",
payment_method_data: { upi: { upi_collect: { vpa_id: "success@upi" } } },
return_url: "https://yourshop.com/checkout/complete",
}),
});
const payment = await res.json();
import requests
res = requests.post(
"https://api.tensorrail.com/payments",
headers={"api-key": "rail_full_test_xxx"},
json={
"amount": 50000,
"currency": "INR",
"customer_id": "cus_8x2Kp",
"setup_future_usage": "off_session",
"confirm": True,
"payment_method": "upi",
"payment_method_type": "upi_collect",
"payment_method_data": {"upi": {"upi_collect": {"vpa_id": "success@upi"}}},
"return_url": "https://yourshop.com/checkout/complete",
},
)
payment = res.json()
Once the payment succeeds, the response carries the token you store against your customer:
{
"payment_id": "pay_N5cPeGw6uS2QIMjnsjVF",
"status": "succeeded",
"customer_id": "cus_8x2Kp",
"payment_method_id": "pm_9c1f7e4a2d6b4f8b8c1e3a5d",
"mandate_id": "man_2d6a4f8b8c1e3a5d9e7b2f10"
}
| Field | Store it? | Use it for |
|---|---|---|
payment_method_id | Yes | Charging the saved method again (one-click and off-session) |
mandate_id | If present | Charging under a mandate the customer authorised for recurring debits |
Keep payment_method_id (and mandate_id, when the method uses a mandate) on your
customer record. That is all you need to charge again.
2. Charge off-session (recurring)
To bill later with no customer present, create a payment that references the saved token
and set off_session. Confirm in the same call.
- curl
- Node
- Python
curl -X POST https://api.tensorrail.com/payments \
-H "api-key: rail_full_test_xxx" \
-H "Idempotency-Key: sub-cus_8x2Kp-2026-08" \
-H "Content-Type: application/json" \
-d '{
"amount": 50000,
"currency": "INR",
"customer_id": "cus_8x2Kp",
"recurring_details": { "type": "payment_method_id", "data": "pm_9c1f7e4a2d6b4f8b8c1e3a5d" },
"off_session": true,
"confirm": true
}'
const res = await fetch("https://api.tensorrail.com/payments", {
method: "POST",
headers: {
"api-key": "rail_full_test_xxx",
"Idempotency-Key": "sub-cus_8x2Kp-2026-08",
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 50000,
currency: "INR",
customer_id: "cus_8x2Kp",
recurring_details: { type: "payment_method_id", data: "pm_9c1f7e4a2d6b4f8b8c1e3a5d" },
off_session: true,
confirm: true,
}),
});
const charge = await res.json();
import requests
res = requests.post(
"https://api.tensorrail.com/payments",
headers={
"api-key": "rail_full_test_xxx",
"Idempotency-Key": "sub-cus_8x2Kp-2026-08",
},
json={
"amount": 50000,
"currency": "INR",
"customer_id": "cus_8x2Kp",
"recurring_details": {"type": "payment_method_id", "data": "pm_9c1f7e4a2d6b4f8b8c1e3a5d"},
"off_session": True,
"confirm": True,
},
)
charge = res.json()
Give each recurring charge a stable, period-based Idempotency-Key (for example one per
customer per billing month). If a retry fires for the same period, the duplicate is
rejected rather than double-charging.
If the method was saved under a mandate, reference mandate_id instead of
recurring_details; everything else about the call is the same.
3. One-click for returning customers
For a returning customer who is present, charge the payment_method_id you stored on
their record. Present your saved-method choice in your own UI, then confirm the payment
against the token — no card fields, no re-entry. The call is the same as an off-session
charge, without off_session (the customer is here to complete any step-up the method
requires):
{
"amount": 50000,
"currency": "INR",
"customer_id": "cus_8x2Kp",
"recurring_details": { "type": "payment_method_id", "data": "pm_9c1f7e4a2d6b4f8b8c1e3a5d" },
"confirm": true,
"return_url": "https://yourshop.com/checkout/complete"
}
If the method needs authentication (for example a 3-D Secure challenge), the response is
requires_customer_action with a next_action — forward the customer to it exactly as in
Accept a payment.
Lifecycle and webhooks
A saved method is backed by a mandate, which has its own lifecycle independent of any one payment:
Recurring charges emit the same events as any other payment (payment_succeeded,
payment_failed, and the intermediate states). Drive your subscription state from those
webhooks rather than the create response, so an off-session charge that completes
asynchronously is reflected correctly. See Handle webhooks.
Subscriptions need a billing processor
Everything above is you charging a saved method on your own schedule: you hold the billing logic, and each charge is an ordinary payment. That is one of two routes, and it is the one this page describes.
The other is the /subscriptions endpoints, where TensorRail holds the schedule, the plan and
the invoice run. Those are driven by a billing processor — a connector class separate from
the rails that move the money — and billing_processor_id is required on every subscription.
The practical consequence is the one to take away: a mandate-capable payment rail does not
switch subscriptions on, and neither does a card rail. They are independent account
configuration. No billing processor is connected today, so a subscription cannot be created and
the call is refused for the missing billing_processor_id. Ask your account manager if you need
one; if you only need recurring charges, build them the way this page describes and you do not
need a billing processor at all.
Security
- You store TensorRail tokens (
pm_…,man_…), never card numbers. The tokens are scoped to your account and are meaningless elsewhere. - The underlying credential is held in TensorRail's vault; your servers and your database never see the underlying credential, which is held in TensorRail's vault.
- Revoking a saved method or mandate stops future charges against it.
Next steps
- Accept a payment — the first-payment flow in full
- Handle webhooks — drive subscription state from events
- Idempotency — safe retries for recurring charges