Crear link de pago recurrente
Crea planes por suscripción (pagos recurrentes) en dólares con la API de vexorpay.
Un link recurrente agrupa hasta 5 planes (precios por ciclo) y permite cobrar automáticamente cada período. El comprador elige un plan en url y paga en el checkout de Polar; a partir de ahí Polar cobra la suscripción cada ciclo.
El link y sus planes se crean en Polar, el Merchant of Record: Polar cobra cada ciclo, calcula el impuesto y acredita cada renovación en tu saldo de Polar. Necesitas Polar conectado (si no, 400 account_not_connected). Ver Suscripciones.
Request
POST /api/v1/payment-links
Headers
| Header | Valor |
|---|---|
Authorization | Bearer vxp_live_tu_clave |
Content-Type | application/json |
Idempotency-Key | opcional — para repetir sin duplicar |
Vexor-Account | opcional — id del usuario conectado, para crear el link en su nombre (cuentas conectadas) |
Body
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
type | string | sí | "recurring". |
prices | array | sí | Entre 1 y 5 planes. |
title | string | no | Título del link. |
description | string | no | Descripción. |
image_file_id | string | no | Foto del producto (archivo tuyo subido desde el dashboard). |
reference | string | no | Referencia tuya para reconciliar. |
metadata | object | no | Pares clave/valor libres. |
Cada elemento de prices
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
amount | number | sí | Precio por ciclo en dólares. Mínimo USD 4, o 0 para un plan gratis de precio fijo (el comprador se suscribe sin tarjeta). |
interval | string | sí | day, week, month o year. |
interval_count | number | no | Cada N intervalos (entero 1–12, default 1). El intervalo year solo admite 1. |
trial_period_days | number | no | Días de prueba gratis (1–90). |
description | string | no | Nombre visible del plan ("Básico", "Pro", "Anual"…). |
pricing_type | string | no | flat (default) o per_seat: el comprador elige cuántos asientos compra. |
per_seat_amount | number | no | Precio de cada asiento en dólares (variante "base + extra"). |
min_seats | integer | no | Asientos mínimos (default 1, máximo 1000). |
max_seats | integer | no | Asientos máximos (sin límite por default). |
seat_tiers | array | no | Matriz de tramos. Cada elemento: up_to (null = sin límite, solo en el último) y unit_amount o flat_amount, en centavos (a diferencia de amount, que va en dólares). |
tiers_mode | string | no | graduated (cada tramo se suma) o volume (se aplica el tramo del total). Requiere seat_tiers. |
Todos los planes deben ser en USD. El link en sí queda con amountCents: 0; el precio real vive en cada plan.
Planes por asientos
Con pricing_type: "per_seat" el plan admite tres formas de precio:
| Variante | Campos | Total para N asientos |
|---|---|---|
| Mismo precio por asiento | solo min_seats/max_seats | N × amount |
| Base + precio por asiento extra | amount + per_seat_amount | amount + (N − 1) × per_seat_amount |
| Tramos por volumen | seat_tiers + tiers_mode | según la matriz (abajo) |
La matriz de seat_tiers arranca en el primer asiento y el último tramo siempre es
abierto (up_to: null). En graduated cada tramo se suma a los anteriores; en
volume la tasa del tramo alcanzado se aplica a todos los asientos. Máximo
10 tramos. Cada tramo lleva unit_amount (por asiento) o flat_amount (cargo
fijo del tramo), en centavos:
{
"amount": 0,
"interval": "month",
"pricing_type": "per_seat",
"tiers_mode": "graduated",
"seat_tiers": [
{ "up_to": 5, "unit_amount": 900 },
{ "up_to": 20, "unit_amount": 700 },
{ "up_to": null, "unit_amount": 500 }
]
}Con esa matriz (graduated), 3 asientos cuestan USD 27, 10 cuestan USD 80 y 30 cuestan USD 200.
La API rechaza con validation_error cualquier key desconocida dentro de un
plan, así que un typo (por ejemplo seats en vez de min_seats) no se ignora en
silencio: falla el request.
Ejemplo
curl https://api.vexorpay.com/api/v1/payment-links \
-X POST \
-H "Authorization: Bearer vxp_live_tu_clave" \
-H "Content-Type: application/json" \
-d '{
"type": "recurring",
"title": "Membresía Vexor",
"reference": "MEMBRESIA",
"prices": [
{
"amount": 9,
"interval": "month",
"description": "Mensual"
},
{
"amount": 90,
"interval": "year",
"trial_period_days": 7,
"description": "Anual"
}
]
}'Respuesta exitosa
201 Created
{
"id": "66f1c1e2c8a4b0d1f2e3a4b6",
"amountCents": 0,
"currency": "usd",
"title": "Membresía Vexor",
"description": null,
"image": null,
"reference": "MEMBRESIA",
"metadata": null,
"status": "active",
"slug": "b2c3d4e5f6",
"sandbox": false,
"url": "https://www.vexorpay.com/p/b2c3d4e5f6",
"checkoutUrl": "https://buy.polar.sh/polar_cl_...",
"type": "recurring",
"prices": [
{
"id": "66f1c1e2c8a4b0d1f2e3a4c1",
"amountCents": 900,
"currency": "usd",
"interval": "month",
"intervalCount": 1,
"trialPeriodDays": null,
"description": "Mensual",
"isDefault": true
},
{
"id": "66f1c1e2c8a4b0d1f2e3a4c2",
"amountCents": 9000,
"currency": "usd",
"interval": "year",
"intervalCount": 1,
"trialPeriodDays": 7,
"description": "Anual",
"isDefault": false
}
],
"createdAt": "2026-01-02T12:00:00.000Z",
"updatedAt": "2026-01-02T12:00:00.000Z"
}checkoutUrl es el checkout de Polar del link. El comprador también puede elegir el plan desde url, o puedes llevarlo directo a un plan con POST /v1/payment-links/{id}/checkout y price_id. El tipo del link no se puede cambiar con PATCH; de los planes existentes se editan el precio, el nombre y la prueba gratis. Agregar o quitar planes, o cambiar el intervalo o los asientos, exige un link nuevo (ver Listar y gestionar links).
Errores
| Error | HTTP | Causa |
|---|---|---|
unauthorized | 401 | Falta el header Authorization. |
invalid_key | 401 | La API key es inválida o fue revocada. |
prices_required | 400 | type: "recurring" sin prices[]. |
too_many_prices | 400 | Más de 5 planes. |
minimum_amount | 400 | Algún plan es menor a USD 4. |
invalid_interval | 400 | interval distinto de day, week, month o year. |
invalid_pricing_type | 400 | pricing_type no es flat ni per_seat. |
invalid_seat_tiers | 400 | Matriz seat_tiers inválida: no es una lista, está vacía, el primer tramo no arranca en el asiento 1, los tramos se pisan, o un plan de precio fijo define tramos. |
invalid_tiers_mode | 400 | tiers_mode no es graduated ni volume, o se indicó sin seat_tiers. |
validation_error | 400 | Otros datos inválidos (p. ej. moneda distinta de USD). |
invalid_interval_count | 400 | interval_count fuera de 1–12 (o year ≠ 1). |
invalid_trial | 400 | trial_period_days fuera de 1–90. |
account_not_connected | 400 | No conectaste Polar (o la conexión se revocó): conéctalo desde el dashboard. |
unsupported_plan | 400 | Un plan no se puede expresar en Polar (p. ej. una combinación de precio por asiento no soportada). |
polar_test_key_not_supported | 400 | Una clave de prueba (vxp_test_...) no puede crear links en producción. Usa una clave de producción. |
polar_not_ready | 409 | La organización de Polar todavía no puede cobrar: completá su verificación en Polar y reintentá. |
idempotency_conflict | 409 | La Idempotency-Key ya se usó con un body distinto. |
polar_provisioning_failed | 500 | Polar rechazó la creación del link. No quedó nada publicado: reintentá. |
rate_limited | 429 | Superaste el límite de 100 requests por minuto. |