PayKitBeta

Subscriptions

Lifecycle states follow the billing state machine (trialing, incomplete, active, past_due, grace, pending_action, suspended, cancelled, …). Dunning is driven automatically by the subscription’s snapshotted policy.

POST/v1/clients/{clientId}/subscriptions

Create a subscription

subscriptions.manage

Create against a plan version; the policy is snapshotted. Starts incomplete (awaiting first payment). Amounts are never sent by you; they come from the catalog (card-testing defense).

Path parameters

clientIdstringYour PayKit client id.

Request body

customerId *stringThe customer id (from Create a customer).
planVersionId *stringThe immutable plan version to subscribe against.
curl -X POST "https://api.paykit.co.za/v1/clients/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6/subscriptions" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"customerId":"7c1f…9a","planVersionId":"pv_9a8b…"}'
201 Created
{
  "subscription": {
    "id": "0f1e2d3c4b5a6978869504132231ffee",
    "state": "incomplete",
    "currentPeriodStart": "2026-07-13T10:00:00Z",
    "currentPeriodEnd": "2026-08-13T10:00:00Z",
    "hasPaymentMethod": false
  }
}
GET/v1/clients/{clientId}/subscriptions/{id}

Get a subscription

subscriptions.read

Fetch a subscription’s current state: state, periods, nextChargeAt, cancelAtPeriodEnd, hasPaymentMethod, plan version and customer ref.

Path parameters

clientIdstringYour PayKit client id.
idstringThe subscription id.
curl -X GET "https://api.paykit.co.za/v1/clients/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6/subscriptions/0f1e2d3c4b5a6978869504132231ffee" \
  -H "Authorization: Bearer YOUR_API_KEY"
200 OK
{
  "subscription": {
    "id": "0f1e2d3c4b5a6978869504132231ffee",
    "state": "active",
    "cancelAtPeriodEnd": false,
    "currentPeriodStart": "2026-07-13T10:00:00Z",
    "currentPeriodEnd": "2026-08-13T10:00:00Z",
    "nextChargeAt": "2026-08-13T10:00:00Z",
    "hasPaymentMethod": true,
    "customer": {
      "externalRef": "user_123"
    },
    "planVersion": {
      "id": "pv_9a8b…",
      "amountMinor": "4990",
      "currency": "ZAR",
      "billingInterval": "month",
      "intervalCount": 1
    }
  }
}
GET/v1/clients/{clientId}/projects/{projectId}/customers/{externalRef}/subscriptions

List a customer's subscriptions

subscriptions.read

List a customer’s subscriptions (all states, newest period first).

Path parameters

clientIdstringYour PayKit client id.
projectIdstringThe project the customer belongs to.
externalRefstringYour id for the user.
curl -X GET "https://api.paykit.co.za/v1/clients/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6/projects/f6e5d4c3b2a1908172635445362718a9/customers/user_123/subscriptions" \
  -H "Authorization: Bearer YOUR_API_KEY"
200 OK
{
  "subscriptions": [
    {
      "id": "0f1e2d3c4b5a6978869504132231ffee",
      "state": "active",
      "cancelAtPeriodEnd": false,
      "currentPeriodEnd": "2026-08-13T10:00:00Z",
      "hasPaymentMethod": true,
      "planVersion": {
        "id": "pv_9a8b…",
        "amountMinor": "4990",
        "currency": "ZAR",
        "billingInterval": "month",
        "intervalCount": 1
      }
    }
  ]
}
POST/v1/clients/{clientId}/subscriptions/{id}/checkout

Start hosted checkout

subscriptions.manage

Hosted checkout for the first payment (incomplete subscriptions only). Redirect the customer to checkoutUrl; callbackUrl is where the gateway returns them afterward.

Path parameters

clientIdstringYour PayKit client id.
idstringThe incomplete subscription id.

Request body

callbackUrl *stringWhere the gateway returns the customer after payment.

Checkout velocity is protected by a card-testing circuit breaker: a burst of attempts for a client trips a temporary 429 card_testing_suspected block.

curl -X POST "https://api.paykit.co.za/v1/clients/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6/subscriptions/0f1e2d3c4b5a6978869504132231ffee/checkout" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"callbackUrl":"https://yourapp.com/billing/return"}'
201 Created
{
  "checkout": {
    "checkoutUrl": "https://checkout.paystack.com/abc123",
    "reference": "chk_9f2a…",
    "environment": "test"
  }
}
POST/v1/clients/{clientId}/subscriptions/{id}/cancel

Cancel a subscription

subscriptions.manage

Cancel immediately, or at the end of the current period with { "atPeriodEnd": true }.

Path parameters

clientIdstringYour PayKit client id.
idstringThe subscription id.

Request body

atPeriodEndbooleanEnd-of-period cancel when true; immediate when omitted.
curl -X POST "https://api.paykit.co.za/v1/clients/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6/subscriptions/0f1e2d3c4b5a6978869504132231ffee/cancel" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"atPeriodEnd":true}'
200 OK
{
  "subscription": {
    "id": "0f1e2d3c4b5a6978869504132231ffee",
    "state": "active",
    "cancelAtPeriodEnd": true
  }
}
POST/v1/clients/{clientId}/subscriptions/{id}/change-plan

Change plan

subscriptions.manage

Switch to a different plan version with credit-based proration.

Path parameters

clientIdstringYour PayKit client id.
idstringThe subscription id.

Request body

planVersionId *stringThe plan version to switch to.
curl -X POST "https://api.paykit.co.za/v1/clients/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6/subscriptions/0f1e2d3c4b5a6978869504132231ffee/change-plan" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"planVersionId":"pv_7d6c…"}'
200 OK
{
  "subscription": {
    "id": "0f1e2d3c4b5a6978869504132231ffee",
    "state": "active",
    "planVersion": {
      "id": "pv_7d6c…"
    }
  }
}