Referencia de APISubscriptions

Subscriptions

/v1/subscriptions — ligan un Customer a un Plan y disparan los cobros recurrentes. Ver también el concepto Suscripción.

El objeto Subscription

{
  "id": "sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
  "customer_id": "cus_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
  "plan_id": "plan_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
  "status": "active",
  "cycle": "monthly",
  "current_period_start": "2026-08-24T00:00:00Z",
  "current_period_end": "2026-09-24T00:00:00Z",
  "next_charge_date": "2026-09-24T00:00:00Z",
  "default_payment_method_id": "pm_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
  "trial_end": null,
  "cancel_at_period_end": false,
  "failed_attempts": 0,
  "created_at": "2026-08-24T15:00:00Z"
}
CampoTipo
idstring
customer_idstring
plan_idstring
statustrialing | active | past_due | paused | paused_insufficient_balance | canceled
cyclemonthly | annual
current_period_start / current_period_enddatetime
next_charge_datedatetime
default_payment_method_idstring
trial_enddatetime | null
cancel_at_period_endboolean
failed_attemptsinteger
created_atdatetime

Crear una suscripción

POST /v1/subscriptions

Request body

{
  "customer_id": "cus_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
  "plan_id": "plan_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
  "cycle": "monthly",
  "payment_method_id": "pm_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
  "trial_days": 7,
  "metadata": { "origen": "landing" }
}
CampoRequerido
customer_id
plan_id
cycleSí (monthly | annual)
payment_method_idNo — requerido si no hay default_payment_method_id en el cliente
trial_daysNo — sobreescribe el trial del plan
start_dateNo
metadataNo

Esta llamada respeta el header Idempotency-Key.

curl
curl -X POST https://api.rekurra.dev/v1/subscriptions \
  -H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: sub-crear-maria-2026-08-24" \
  -d '{
    "customer_id": "cus_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
    "plan_id": "plan_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
    "cycle": "monthly",
    "payment_method_id": "pm_01HZXK7QJ5M8Z9X2R3TVQ7A1B2"
  }'
JavaScript (fetch)
const res = await fetch("https://api.rekurra.dev/v1/subscriptions", {
  method: "POST",
  headers: {
    Authorization: "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX",
    "Content-Type": "application/json",
    "Idempotency-Key": "sub-crear-maria-2026-08-24",
  },
  body: JSON.stringify({
    customer_id: "cus_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
    plan_id: "plan_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
    cycle: "monthly",
    payment_method_id: "pm_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
  }),
});
const subscription = await res.json();
Python (requests)
import requests
 
res = requests.post(
    "https://api.rekurra.dev/v1/subscriptions",
    headers={
        "Authorization": "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX",
        "Content-Type": "application/json",
        "Idempotency-Key": "sub-crear-maria-2026-08-24",
    },
    json={
        "customer_id": "cus_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
        "plan_id": "plan_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
        "cycle": "monthly",
        "payment_method_id": "pm_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
    },
)
subscription = res.json()

Response — 201 Created

Devuelve el objeto Subscription, con status: trialing si el plan (o trial_days) define un periodo de prueba, o status: active si el primer cobro se ejecuta de inmediato.

Errores posibles

Statuserror.typeCausa
400invalid_requestCampos faltantes o cycle inválido
404invalid_requestcustomer_id o plan_id no existen
409invalid_requestConflicto de Idempotency-Key con un body distinto
422insufficient_balanceTu saldo prepago no cubre el fee proyectado
422wompi_errorEl primer cobro fue rechazado por Wompi

Listar suscripciones

GET /v1/subscriptions

Filtros opcionales: ?status=, ?customer_id=, ?plan_id=, más paginación estándar.

curl
curl "https://api.rekurra.dev/v1/subscriptions?status=active" \
  -H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"
JavaScript (fetch)
const res = await fetch(
  "https://api.rekurra.dev/v1/subscriptions?status=active",
  { headers: { Authorization: "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" } }
);
const { data } = await res.json();
Python (requests)
import requests
 
res = requests.get(
    "https://api.rekurra.dev/v1/subscriptions",
    params={"status": "active"},
    headers={"Authorization": "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"},
)
body = res.json()

Obtener una suscripción

GET /v1/subscriptions/{id}

curl
curl https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2 \
  -H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"
JavaScript (fetch)
const res = await fetch(
  "https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
  { headers: { Authorization: "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" } }
);
const subscription = await res.json();
Python (requests)
import requests
 
res = requests.get(
    "https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
    headers={"Authorization": "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"},
)
subscription = res.json()

Errores posibles

Statuserror.typeCausa
404invalid_requestLa suscripción no existe

Cancelar una suscripción

POST /v1/subscriptions/{id}/cancel

{ "at_period_end": true }
curl
curl -X POST https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/cancel \
  -H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{ "at_period_end": true }'
JavaScript (fetch)
const res = await fetch(
  "https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/cancel",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ at_period_end: true }),
  }
);
const subscription = await res.json();
Python (requests)
import requests
 
res = requests.post(
    "https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/cancel",
    headers={
        "Authorization": "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX",
        "Content-Type": "application/json",
    },
    json={"at_period_end": True},
)
subscription = res.json()

Si at_period_end es true (u omitido), la suscripción sigue active hasta el fin del periodo actual con cancel_at_period_end: true, y luego pasa a canceled. Si es false, cancela de inmediato.


Pausar / reanudar

POST /v1/subscriptions/{id}/pause · POST /v1/subscriptions/{id}/resume

curl
curl -X POST https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/pause \
  -H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"
JavaScript (fetch)
await fetch(
  "https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/resume",
  {
    method: "POST",
    headers: { Authorization: "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" },
  }
);
Python (requests)
import requests
 
requests.post(
    "https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/resume",
    headers={"Authorization": "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"},
)

Cambiar de plan

POST /v1/subscriptions/{id}/change-plan

{ "plan_id": "plan_01HZ...", "cycle": "annual", "prorate": true }
curl
curl -X POST https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/change-plan \
  -H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{ "plan_id": "plan_01HZ...", "cycle": "annual", "prorate": true }'
JavaScript (fetch)
const res = await fetch(
  "https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/change-plan",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ plan_id: "plan_01HZ...", cycle: "annual", prorate: true }),
  }
);
const subscription = await res.json();
Python (requests)
import requests
 
res = requests.post(
    "https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/change-plan",
    headers={
        "Authorization": "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX",
        "Content-Type": "application/json",
    },
    json={"plan_id": "plan_01HZ...", "cycle": "annual", "prorate": True},
)
subscription = res.json()

Forzar un reintento de cobro

POST /v1/subscriptions/{id}/charge-now

curl
curl -X POST https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/charge-now \
  -H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"
JavaScript (fetch)
const res = await fetch(
  "https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/charge-now",
  {
    method: "POST",
    headers: { Authorization: "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" },
  }
);
const charge = await res.json();
Python (requests)
import requests
 
res = requests.post(
    "https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/charge-now",
    headers={"Authorization": "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"},
)
charge = res.json()

Errores posibles (acciones)

Statuserror.typeCausa
404invalid_requestLa suscripción no existe
409invalid_requestLa acción no es válida para el estado actual (ej. pause en una ya canceled)
422insufficient_balanceSaldo prepago insuficiente para procesar el cobro
422wompi_errorWompi rechazó el cobro