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"
}| Campo | Tipo |
|---|---|
id | string |
customer_id | string |
plan_id | string |
status | trialing | active | past_due | paused | paused_insufficient_balance | canceled |
cycle | monthly | annual |
current_period_start / current_period_end | datetime |
next_charge_date | datetime |
default_payment_method_id | string |
trial_end | datetime | null |
cancel_at_period_end | boolean |
failed_attempts | integer |
created_at | datetime |
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" }
}| Campo | Requerido |
|---|---|
customer_id | Sí |
plan_id | Sí |
cycle | Sí (monthly | annual) |
payment_method_id | No — requerido si no hay default_payment_method_id en el cliente |
trial_days | No — sobreescribe el trial del plan |
start_date | No |
metadata | No |
Esta llamada respeta el header Idempotency-Key.
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"
}'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();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
| Status | error.type | Causa |
|---|---|---|
400 | invalid_request | Campos faltantes o cycle inválido |
404 | invalid_request | customer_id o plan_id no existen |
409 | invalid_request | Conflicto de Idempotency-Key con un body distinto |
422 | insufficient_balance | Tu saldo prepago no cubre el fee proyectado |
422 | wompi_error | El 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 "https://api.rekurra.dev/v1/subscriptions?status=active" \
-H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"const res = await fetch(
"https://api.rekurra.dev/v1/subscriptions?status=active",
{ headers: { Authorization: "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" } }
);
const { data } = await res.json();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 https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2 \
-H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"const res = await fetch(
"https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
{ headers: { Authorization: "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" } }
);
const subscription = await res.json();import requests
res = requests.get(
"https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
headers={"Authorization": "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"},
)
subscription = res.json()Errores posibles
| Status | error.type | Causa |
|---|---|---|
404 | invalid_request | La suscripción no existe |
Cancelar una suscripción
POST /v1/subscriptions/{id}/cancel
{ "at_period_end": true }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 }'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();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 -X POST https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/pause \
-H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"await fetch(
"https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/resume",
{
method: "POST",
headers: { Authorization: "Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" },
}
);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 -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 }'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();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 -X POST https://api.rekurra.dev/v1/subscriptions/sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/charge-now \
-H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"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();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)
| Status | error.type | Causa |
|---|---|---|
404 | invalid_request | La suscripción no existe |
409 | invalid_request | La acción no es válida para el estado actual (ej. pause en una ya canceled) |
422 | insufficient_balance | Saldo prepago insuficiente para procesar el cobro |
422 | wompi_error | Wompi rechazó el cobro |