CentraPoint

API

Customer subscriptions API

Manage subscription packages and plans, subscribe customers to plans, and pause, resume, cancel or change their subscriptions.

On this page

Overview#

These endpoints manage the subscription catalogue (packages and their plans) and the customers subscribed to it. Read the Packages, plans & customer subscriptions guide first for how trials, renewals, dunning and the collection methods work. For card subscriptions created by recurring payment links, see the separate Subscriptions API.

Customer subscription endpoints
EndpointPurpose
GET /api/v1/packagesList packages with their plans
POST /api/v1/packagesCreate a package (optionally with plans)
GET / PATCH / DELETE /api/v1/packages/{id}Get, update or disable a package
POST /api/v1/packages/{id}/plansAdd a plan
GET / PATCH /api/v1/plans/{id}Get or update a plan
POST /api/v1/customer-subscriptionsSubscribe a customer
GET /api/v1/customer-subscriptionsList subscriptions
GET / PATCH /api/v1/customer-subscriptions/{id}Get or update one
POST …/{id}/pause, /resume, /cancel, /change-planLifecycle actions

Packages#

GET/api/v1/packages

Returns { "data": [ … ] } in sort order, each package with its plans. ?enabled=true returns only enabled packages with only their enabled plans (use this to build your own pricing page); ?enabled=false only disabled packages. limit is 1–100, default 50.

POST/api/v1/packages
Package fields
FieldTypeDescription
namerequiredstring1–120 characters.
descriptionoptionalstring | nullUp to 2000 characters.
enabledoptionalbooleanDefault true.
sortOrderoptionalinteger-10000 to 10000.
defaultProviderIdoptionalstring | nullPayment provider used for gateway subscriptions when none is given.
plansoptionalPlan[]Up to 20 plans to create with the package (fields as in Plans). Create only.
curl -X POST "https://app.centrapoint.co.za/api/v1/packages" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Idempotency-Key: 5c2e8a1f-3b7d-4e9a-8f1c-2d6b0a4e7c93" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Cloud",
    "description": "Hosted project management",
    "plans": [
      {
        "name": "Pro",
        "frequency": "monthly",
        "amount": 499,
        "trialDays": 14,
        "features": [
          "Unlimited projects",
          "Priority support"
        ]
      }
    ]
  }'

GET /api/v1/packages/{id} returns the package with all its plans. PATCH updates any of the fields above except plans (unknown fields are rejected). DELETE does not delete anything: it disables the package so it can't be subscribed to, and existing subscriptions keep billing.

Plans#

POST/api/v1/packages/{id}/plans
Plan fields
FieldTypeDescription
namerequiredstring1–120 characters.
frequencyrequiredstringdaily, weekly, biweekly, monthly, quarterly, biannually or annually.
amountrequirednumberPrice per period, 0–100 000 000. 0 = free plan.
currencyoptionalstringISO 4217 code, default ZAR.
setupFeeoptionalnumberAdded to the first charge.
trialDaysoptionalinteger0–730.
billingCyclesoptionalinteger | null1–1000 fixed cycles; null = until cancelled.
descriptionoptionalstring | nullUp to 2000 characters.
featuresoptionalstring[]Up to 50 bullet points of up to 200 characters.
providerIdsoptionalstring[]Payment providers allowed to collect the plan; empty = any.
hasCallToAction, callToActionUrloptionalboolean, string | nullShow a button to your http(s) URL instead of a purchase option (URL required).
hasContactUsoptionalbooleanShow a contact option.
enabledoptionalbooleanDefault true.
sortOrderoptionalinteger-10000 to 10000.
curl -X POST "https://app.centrapoint.co.za/api/v1/packages/cmg4p1a2b0001pkg0001abcd/plans" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Idempotency-Key: 5c2e8a1f-3b7d-4e9a-8f1c-2d6b0a4e7c93" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Team (annual)",
    "frequency": "annually",
    "amount": 4990,
    "features": [
      "Up to 25 users"
    ]
  }'

GET /api/v1/plans/{id} returns a plan; PATCH /api/v1/plans/{id} updates any of its fields. Price and frequency changes apply to new subscriptions only. The plan object also has id, object: "plan", packageId, createdAt and updatedAt.

The customer subscription object#

Customer subscription
{
  "id": "cmg4s9d8e0005csb0001abcd",
  "object": "customer_subscription",
  "status": "trialing",
  "customer": {
    "id": "cmg2c0s7t0003cust0001abcd",
    "externalReference": "CRM-1001",
    "email": "[email protected]"
  },
  "plan": {
    "id": "cmg4p1a2b0002pln0001abcd",
    "name": "Pro",
    "packageId": "cmg4p1a2b0001pkg0001abcd",
    "packageName": "Acme Cloud"
  },
  "collectionMethod": "invoice",
  "quantity": 1,
  "amount": 499,
  "currency": "ZAR",
  "frequency": "monthly",
  "billingCycles": null,
  "cyclesBilled": 0,
  "startedAt": "2026-09-28T08:10:00.000Z",
  "trialEndsAt": "2026-10-12T08:10:00.000Z",
  "currentPeriodStart": null,
  "currentPeriodEnd": null,
  "nextBillingAt": "2026-10-12T08:10:00.000Z",
  "endsAt": null,
  "cancelAtPeriodEnd": false,
  "cancelledAt": null,
  "cancelReason": null,
  "pausedAt": null,
  "resumedAt": null,
  "dunningAttempts": 0,
  "gateway": null,
  "providerId": null,
  "paymentTokenId": null,
  "couponId": null,
  "externalReference": "SUB-CRM-1001-PRO",
  "metadata": {
    "crmDealId": "D-778"
  },
  "source": "api",
  "createdAt": "2026-09-28T08:10:00.000Z",
  "updatedAt": "2026-09-28T08:10:00.000Z"
}
Customer subscription fields
FieldTypeDescription
statusrequiredstringincomplete, trialing, active, past_due, paused, cancelled or expired (lifecycle).
customer, planrequiredobjectSummaries of the customer and plan (with its package).
collectionMethodrequiredstringgateway, debit_order or invoice.
quantity, amount, currency, frequencyrequiredamount = plan price × quantity (less a forever coupon), per period.
billingCycles, cyclesBilledrequiredinteger | null, integerFixed term from the plan and cycles billed so far.
trialEndsAt, currentPeriodStart, currentPeriodEndrequiredstring | nullTrial end and the current paid period.
nextBillingAtrequiredstring | nullNext charge (trial end during a trial); null when nothing more will be billed.
endsAt, cancelAtPeriodEndrequiredstring | null, booleanScheduled end (cancellation at period end or fixed term).
cancelledAt, cancelReason, pausedAt, resumedAtrequiredstring | null
dunningAttemptsrequiredintegerFailed-payment attempts so far; 0 when paid up.
gateway, providerId, paymentTokenIdrequiredstring | nullCollecting provider; paymentTokenId is the gateway subscription (see Subscriptions API).
couponIdrequiredstring | nullCoupon applied at sign-up.
externalReference, metadatarequiredstring | null, objectYour ID and key/value data.
sourcerequiredstringdashboard, api, checkout or portal.
eventsoptionalarrayOnly with ?include=events: latest 100 timeline events { id, type, details, createdAt }, newest first.

Subscribe a customer#

POST/api/v1/customer-subscriptions
Create fields
FieldTypeDescription
customerId or customerrequiredstring | objectExactly one: the customer ID, or { "id" } / { "externalReference" }.
planIdrequiredstringAn enabled plan of an enabled package.
collectionMethodrequiredstringgateway, debit_order or invoice (details).
providerIdoptionalstringGateway or Netcash provider; defaults to the package's default gateway or the mandate's provider.
quantityoptionalinteger1–10000, default 1.
couponCodeoptionalstringNeeds Coupons in your plan; must be valid for the plan and customer.
trialoptionalbooleanfalse skips the plan's trial.
trialDaysoptionalinteger0–730; overrides the plan's trial length.
mandateIdoptionalstringdebit_order only: a mandate of this customer with the plan's frequency, not linked to another subscription.
externalReferenceoptionalstringUp to 190 characters. Repeating the call with the same value returns the existing subscription (200).
metadataoptionalobjectKey/value data (rules). With a coupon, the key coupon is reserved for the coupon terms snapshot (see Coupons).
sendEmailoptionalbooleanDefault true: email the first invoice, payment request or trial confirmation.
curl -X POST "https://app.centrapoint.co.za/api/v1/customer-subscriptions" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Idempotency-Key: 5c2e8a1f-3b7d-4e9a-8f1c-2d6b0a4e7c93" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": {
      "externalReference": "CRM-1001"
    },
    "planId": "cmg4p1a2b0002pln0001abcd",
    "collectionMethod": "invoice",
    "externalReference": "SUB-CRM-1001-PRO",
    "metadata": {
      "crmDealId": "D-778"
    }
  }'

Returns 201 with the subscription plus a payment object, or 200 when a subscription with the same externalReference already exists. Supports the Idempotency-Key header.

The payment object#

Payment fields
FieldTypeDescription
duerequiredbooleanMoney is due before the subscription becomes active.
amount, currencyrequirednumber, stringWhat is due now (setup fee + first period, after coupons).
methodrequiredstringinvoice: an invoice with a pay link was created; link: a payment link (gateway method); token: a stored card was charged; none: nothing to collect now (trial, free plan, debit order collects on its debit day); checkout: on a replay of an incomplete gateway subscription without an open payment request.
payUrlrequiredstring | nullSend the customer here to pay. Already emailed unless sendEmail was false.
invoiceId, paymentLinkId, transactionIdrequiredstring | nullThe document created for the charge.
payment (no trial, invoice method)
{
  "due": true,
  "amount": 499,
  "currency": "ZAR",
  "method": "invoice",
  "payUrl": "https://app.centrapoint.co.za/pay/4hQ2…",
  "invoiceId": "cmg2i4n5v0004inv0001abcd",
  "paymentLinkId": null,
  "transactionId": null
}

List customer subscriptions#

GET/api/v1/customer-subscriptions
List query parameters
FieldTypeDescription
customerId, planId, packageIdoptionalstringFilters.
statusoptionalstringOne of the statuses above; anything else returns 400.
externalReferenceoptionalstringExact match.
limitoptionalinteger1–100, default 20.

Returns { "data": [ … ] }, newest first.

curl -X GET "https://app.centrapoint.co.za/api/v1/customer-subscriptions?customerId=cmg2c0s7t0003cust0001abcd&status=active" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY"

Get a customer subscription#

GET/api/v1/customer-subscriptions/{id}
curl -X GET "https://app.centrapoint.co.za/api/v1/customer-subscriptions/cmg4s9d8e0005csb0001abcd?include=events" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY"

Update a customer subscription#

PATCH/api/v1/customer-subscriptions/{id}
Update fields
FieldTypeDescription
metadataoptionalobject | nullReplaces the whole map; null clears it. On a subscription with a coupon, the key coupon is reserved: CentraPoint stores the coupon terms there, keeps them when you replace the map and leaves them out of responses.
externalReferenceoptionalstring | nullMust not belong to another subscription.
nextBillingAtoptionalstring (date-time)ISO date-time with offset. Moves the next charge (and the trial end during a trial). Only for active, trialing, past_due or paused subscriptions; not for debit order or gateway-billed ones; in the future, within 3 years and before endsAt.
curl -X PATCH "https://app.centrapoint.co.za/api/v1/customer-subscriptions/cmg4s9d8e0005csb0001abcd" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "nextBillingAt": "2026-10-19T08:00:00+02:00"
  }'

Unknown fields are rejected. Sends subscription.updated when something changed.

Pause and resume#

POST/api/v1/customer-subscriptions/{id}/pause
POST/api/v1/customer-subscriptions/{id}/resume

No body (an empty object is accepted). Pause is allowed from active or past_due; resume from paused. Repeating either when it has already taken effect returns the subscription unchanged. Gateway-billed subscriptions are paused and resumed at the gateway first; if the gateway can't (Netcash Pay Now) or refuses, you get 400 and nothing changes. Linked debit order mandates are paused and resumed with the subscription.

curl -X POST "https://app.centrapoint.co.za/api/v1/customer-subscriptions/cmg4s9d8e0005csb0001abcd/pause" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY"

Cancel#

POST/api/v1/customer-subscriptions/{id}/cancel
Cancel fields
FieldTypeDescription
atPeriodEndoptionalbooleantrue: cancel when the paid period (or trial) ends. Default: cancel now.
reasonoptionalstringUp to 1000 characters; stored as cancelReason and shown to the customer.
curl -X POST "https://app.centrapoint.co.za/api/v1/customer-subscriptions/cmg4s9d8e0005csb0001abcd/cancel" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "atPeriodEnd": true,
    "reason": "Moving to another provider"
  }'

Gateway billing is stopped first; if the gateway refuses, you get 400 and the subscription is not cancelled. Cancelling a cancelled or expired subscription returns it unchanged. A cancellation at period end sets cancelAtPeriodEnd: true and endsAt, and sends subscription.updated; the subscription is cancelled (with subscription.cancelled) when endsAt passes.

Change plan#

POST/api/v1/customer-subscriptions/{id}/change-plan
Change plan fields
FieldTypeDescription
planIdrequiredstringAn enabled plan in the same currency (it may be in another package).
quantityoptionalinteger1–10000; default the current quantity.
curl -X POST "https://app.centrapoint.co.za/api/v1/customer-subscriptions/cmg4s9d8e0005csb0001abcd/change-plan" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "planId": "cmg4p1a2b0003pln0002abcd"
  }'

The new amount and frequency apply from the next billing date; the current period is not prorated. The customer gets the upgraded or downgraded email (by comparing the daily price) and subscription.plan_changed is sent with previousPlan and previousAmount. Not allowed for cancelled or expired subscriptions, for gateway-billed subscriptions when the amount or frequency would change, or for a frequency change on a debit order subscription.

Errors#

Customer subscription errors
StatuserrorWhen
400invalid_requestValidation failed or the action isn't allowed: unknown customer/plan/mandate/provider, inactive customer, disabled plan, invalid coupon, currency mismatch, wrong status for the action, the gateway refused. Nothing changed.
403plan_restrictedNo REST API or Recurring billing in your plan.
403plan_limitThe collection method needs Invoicing or Debit orders, which your plan lacks.
403account_restrictedThe account is read-only (writes).
404not_foundNo package, plan or subscription with this ID in your organisation.
409idempotency_conflictIdempotency-Key reused with a different body (POST).
401 / 429 / 500unauthorized / rate_limited / internal_errorSee Errors