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.
/v1/clients/{clientId}/subscriptionsCreate a subscription
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
| clientId | string | Your PayKit client id. |
Request body
| customerId * | string | The customer id (from Create a customer). |
| planVersionId * | string | The 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…"}'{
"subscription": {
"id": "0f1e2d3c4b5a6978869504132231ffee",
"state": "incomplete",
"currentPeriodStart": "2026-07-13T10:00:00Z",
"currentPeriodEnd": "2026-08-13T10:00:00Z",
"hasPaymentMethod": false
}
}/v1/clients/{clientId}/subscriptions/{id}Get a subscription
Fetch a subscription’s current state: state, periods, nextChargeAt, cancelAtPeriodEnd, hasPaymentMethod, plan version and customer ref.
Path parameters
| clientId | string | Your PayKit client id. |
| id | string | The subscription id. |
curl -X GET "https://api.paykit.co.za/v1/clients/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6/subscriptions/0f1e2d3c4b5a6978869504132231ffee" \ -H "Authorization: Bearer YOUR_API_KEY"
{
"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
}
}
}/v1/clients/{clientId}/projects/{projectId}/customers/{externalRef}/subscriptionsList a customer's subscriptions
List a customer’s subscriptions (all states, newest period first).
Path parameters
| clientId | string | Your PayKit client id. |
| projectId | string | The project the customer belongs to. |
| externalRef | string | Your 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"
{
"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
}
}
]
}/v1/clients/{clientId}/subscriptions/{id}/checkoutStart hosted checkout
Hosted checkout for the first payment (incomplete subscriptions only). Redirect the customer to checkoutUrl; callbackUrl is where the gateway returns them afterward.
Path parameters
| clientId | string | Your PayKit client id. |
| id | string | The incomplete subscription id. |
Request body
| callbackUrl * | string | Where 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"}'{
"checkout": {
"checkoutUrl": "https://checkout.paystack.com/abc123",
"reference": "chk_9f2a…",
"environment": "test"
}
}/v1/clients/{clientId}/subscriptions/{id}/cancelCancel a subscription
Cancel immediately, or at the end of the current period with { "atPeriodEnd": true }.
Path parameters
| clientId | string | Your PayKit client id. |
| id | string | The subscription id. |
Request body
| atPeriodEnd | boolean | End-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}'{
"subscription": {
"id": "0f1e2d3c4b5a6978869504132231ffee",
"state": "active",
"cancelAtPeriodEnd": true
}
}/v1/clients/{clientId}/subscriptions/{id}/change-planChange plan
Switch to a different plan version with credit-based proration.
Path parameters
| clientId | string | Your PayKit client id. |
| id | string | The subscription id. |
Request body
| planVersionId * | string | The 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…"}'{
"subscription": {
"id": "0f1e2d3c4b5a6978869504132231ffee",
"state": "active",
"planVersion": {
"id": "pv_7d6c…"
}
}
}