# Suscripciones

Lista, consulta, cancela y gestiona las suscripciones de tus links recurrentes.

> [!WARNING]
> El SDK `vexor` de npm (Stripe, MercadoPago, PayPal, Square, Talo) está **deprecado**: ya no se mantiene y no debe usarse. vexorpay es hoy una **API REST de links de pago en USD sobre Polar (Merchant of Record), sin SDK oficial**. Migración: <https://developers.vexorpay.com/docs/migracion-desde-el-sdk>

Fuente: <https://developers.whop.vexorpay.com/docs/api/subscriptions>

Las suscripciones se crean cuando un comprador paga uno de los planes de tu [link recurrente](/docs/api/create-recurring-payment-link). Polar (el Merchant of Record) cobra cada ciclo y acredita cada renovación en tu saldo de Polar, y puedes enterarte de cada cobro por el webhook [`subscription.payment.completed`](/docs/api/webhooks).

## El objeto Subscription

```json
{
  "id": "66f1c1e2c8a4b0d1f2e3a4b7",
  "provider": "polar",
  "polarSubscriptionId": "5c2e7a1b-...",
  "paymentLinkId": "66f1c1e2c8a4b0d1f2e3a4b6",
  "paymentLinkPriceId": "66f1c1e2c8a4b0d1f2e3a4c1",
  "price": {
    "amountCents": 900,
    "currency": "usd",
    "interval": "month",
    "intervalCount": 1,
    "trialPeriodDays": null,
    "seats": 1,
    "seatTotalCents": 900,
    "perSeatAmountCents": null
  },
  "status": "active",
  "customerEmail": "cliente@ejemplo.com",
  "reference": "ORD-1001",
  "metadata": null,
  "currentPeriodStart": "2026-01-02T12:00:00.000Z",
  "currentPeriodEnd": "2026-02-02T12:00:00.000Z",
  "canceledAt": null,
  "cancelAtPeriodEnd": false,
  "trialStart": null,
  "trialEnd": null,
  "sandbox": false,
  "createdAt": "2026-01-02T12:00:00.000Z"
}
```

Estados posibles (`status`): `active`, `trialing`, `past_due`, `canceled`, `unpaid`, `pending`, `incomplete`, `incomplete_expired` y `paused` (los de Polar). El filtro `status` del listado acepta `active`, `trialing`, `past_due`, `canceled`, `unpaid`, `pending` e `incomplete`.

`provider` es siempre `"polar"` y `polarSubscriptionId` trae el id de la suscripción en Polar.
`reference` y `metadata`
son los tuyos del checkout que creó la suscripción (ver [Identificar cada venta](/docs/api/orders#identificar-cada-venta-con-tu-referencia));
el listado acepta `?reference=`. `trialStart` y `trialEnd` son `null`, y `price.seatTotalCents` es el total por ciclo que informa Polar.
`paymentLinkId` y `paymentLinkPriceId` vienen vacíos si el producto de Polar no es de un link de
vexorpay (p. ej. uno creado directamente en Polar).

## Listar suscripciones

`GET /api/v1/subscriptions`

| Query param | Descripción |
| --- | --- |
| `status` | Filtra por estado. |
| `payment_link_id` | Filtra por link. |
| `limit` | Tamaño de página (default 10, máx 100). |
| `starting_after` / `ending_before` | Paginación por cursor (id de suscripción). |

```bash
curl "https://api.vexorpay.com/api/v1/subscriptions?status=active" \
  -H "Authorization: Bearer vxp_live_tu_clave"
```

La respuesta usa el formato de lista con `data`, `has_more` y `total_count`.

## Ver una suscripción

`GET /api/v1/subscriptions/{id}`

El entorno lo define la API key: con una clave `vxp_test_...` operas sobre suscripciones de prueba.

```bash
curl "https://api.vexorpay.com/api/v1/subscriptions/66f1c1e2c8a4b0d1f2e3a4b7" \
  -H "Authorization: Bearer vxp_live_tu_clave"
```

## Cancelar una suscripción

`POST /api/v1/subscriptions/{id}/cancel`

Programa la cancelación en Polar **al final del período** (`cancelAtPeriodEnd: true`): el cliente conserva el acceso hasta que termine el ciclo pago y no se le vuelve a cobrar. Es idempotente: cancelar una suscripción ya cancelada responde `200` con su estado actual.

El cuerpo es `{}`. El entorno lo define la API key, no el body.

```bash
curl https://api.vexorpay.com/api/v1/subscriptions/66f1c1e2c8a4b0d1f2e3a4b7/cancel \
  -X POST \
  -H "Authorization: Bearer vxp_live_tu_clave" \
  -H "Content-Type: application/json" \
  -d '{}'
```

## Cambiar la cantidad de asientos

`PATCH /api/v1/subscriptions/{id}`

En planes `per_seat` puedes cambiar cuántos asientos tiene el suscriptor:

```bash
curl https://api.vexorpay.com/api/v1/subscriptions/66f1c1e2c8a4b0d1f2e3a4b7 \
  -X PATCH \
  -H "Authorization: Bearer vxp_live_tu_clave" \
  -H "Content-Type: application/json" \
  -d '{ "seats": 8 }'
```

Valida los límites del plan: si `seats` cae fuera de `minSeats`/`maxSeats` responde
`invalid_seats`. El cambio se hace en Polar y la diferencia se factura al instante (cobro al
agregar asientos, crédito a favor del comprador al quitarlos); los ciclos siguientes se cobran
por el nuevo total. Sólo se puede en suscripciones `active` o `trialing` sin cancelación
programada; si Polar rechaza el cambio responde `502 polar_update_failed`.

El entorno lo define la API key: con una clave `vxp_test_...` operas sobre suscripciones de prueba.

## Abrir el portal del cliente

`POST /api/v1/subscriptions/{id}/portal`

Devuelve `{ "url": "https://polar.sh/..." }` (el portal del cliente de Polar) para que el suscriptor gestione su método de pago, vea facturas o cancele por su cuenta. Redirige al comprador a esa URL.

```bash
curl https://api.vexorpay.com/api/v1/subscriptions/66f1c1e2c8a4b0d1f2e3a4b7/portal \
  -X POST \
  -H "Authorization: Bearer vxp_live_tu_clave" \
  -H "Content-Type: application/json" \
  -d '{}'
```

## Errores

| Error | HTTP | Causa |
| --- | --- | --- |
| `unauthorized` | 401 | Falta el header `Authorization`. |
| `invalid_key` | 401 | La API key es inválida o fue revocada. |
| `validation_error` | 400 | `status` inválido en el listado, cursor inexistente, o un campo distinto de `seats` (o sin `seats`) en el `PATCH`. |
| `subscription_not_found` | 404 | No existe esa suscripción, o es de otro modo (`sandbox`). |
| `price_not_found` | 404 | El plan de la suscripción ya no existe (cambio de asientos). |
| `invalid_seats` | 400 | `seats` fuera de `minSeats`/`maxSeats` del plan, `seats` en un plan de precio fijo, o suscripción no activa o con cancelación programada. |
| `polar_update_failed` | 502 | Polar rechazó o no respondió la cancelación, el cambio de asientos o el portal. Reintenta. |
| `polar_reconnect_required` | 409 | La conexión de Polar no tiene el permiso de suscripciones: el creador reconecta Polar. |
| `unexpected_error` | 500 | Error interno inesperado. |
| `rate_limited` | 429 | Superaste el límite de 100 requests por minuto. |
