Subscriptions

Recurring billing on your spuke account

Subscriptions

Create recurring subscriptions for customers using a recurring price from your catalog. Subscriptions run on your connected spuke account; spuke deducts the platform fee automatically from each renewal invoice.

Base URL: https://api.spuke.com/v1 Auth: Authorization: Bearer sk_live_… (or sk_test_…)

Prerequisites

  1. Create a product and a recurring price (see Products and Prices).
  2. The price must have recurring.interval set (day, week, month, year).

Create a subscription

POST /v1/subscriptions

curl -X POST https://api.spuke.com/v1/subscriptions \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: sub_20260718_001" \
  -d '{
    "price_id": "price_1Tu…",
    "customer_name":  "Jane Doe",
    "customer_email": "jane@example.com",
    "quantity": 1,
    "trial_days": 14,
    "metadata": { "plan": "pro" }
  }'

Body

Field Type Required Notes
price_id string Recurring price from your catalog.
customer_name string 2–120 chars.
customer_email string Valid email. Receipts and dunning are sent here.
quantity integer Defaults to 1.
trial_days integer Free trial before first charge.
metadata object Free-form key/value returned in webhooks.

Response

{
  "id": "sub_1Tu…",
  "object": "subscription",
  "status": "incomplete",
  "customer": "cus_1Tu…",
  "price_id": "price_1Tu…",
  "quantity": 1,
  "currency": "EUR",
  "amount": 1990,
  "current_period_start": 1735689600,
  "current_period_end":   1738368000,
  "latest_invoice": "in_1Tu…",
  "client_secret": "pi_1Tu…_secret_…",
  "trial_end": null,
  "created": 1735689600
}

The subscription is created in incomplete state. Use the returned client_secret on the customer's browser to confirm the first payment. Once confirmed the status transitions to active (or trialing if trial_days was set) and a customer.subscription.updated webhook is fired.

Retrieve

GET /v1/subscriptions/{id}

List

GET /v1/subscriptions?limit=20

Returns up to 100 subscriptions ordered by creation date (newest first).

Cancel

POST /v1/subscriptions/{id}/cancel

curl -X POST https://api.spuke.com/v1/subscriptions/sub_1Tu…/cancel \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: cancel_sub_1Tu_20260718" \
  -d '{ "at_period_end": true }'
Field Default Meaning
at_period_end true Keep active until the current period ends, then cancel.
at_period_end: false Cancel immediately. No further invoices.

Status model

Status Meaning
incomplete Waiting for the first payment confirmation.
incomplete_expired First payment was not confirmed within 23 h.
trialing Free trial in progress.
active Paid and current.
past_due Renewal failed. Dunning in progress.
unpaid All retries exhausted, subscription frozen.
canceled Terminated.

Fees

Recurring invoices carry a platform fee computed from your plan and payment method. Fees are settled in EUR and deducted per invoice — same rules as one-off Checkout Sessions. See Fees, currency & minimum amounts.

Idempotency

Send Idempotency-Key: <your-key> on POST /v1/subscriptions and POST /v1/subscriptions/{id}/cancel. Keys are scoped to (merchant_id, method, path) and expire after 24 hours.

  • Same key + same body → original response replayed verbatim.
  • Same key + different body409 Conflict with type: "idempotency_error", code: "idempotency_key_reused". See Error reference.

Errors

HTTP error.type error.code Meaning
400 invalid_request field-specific Missing / malformed field, or price is not recurring.
401 authentication_error invalid_api_key Bad, revoked or wrong-mode key.
404 not_found price_not_found / subscription_not_found Not owned by your account.
409 idempotency_error idempotency_key_reused Same key with different body.
409 resource_conflict merchant_not_ready / subscription_already_canceled See message.
429 rate_limit_error rate_limited Respect Retry-After.

Webhooks

Listen for these events (see Event reference for payloads):

  • customer.subscription.created / updated / deleted
  • customer.subscription.trial_will_end — fires 3 days before the trial ends
  • invoice.payment_succeeded — renewal charged
  • invoice.payment_failed — dunning stage advanced (notice → warning → final)