REST API
Headcount exposes JSON endpoints for plans, subscriptions, checkout sessions, the customer portal, and member info. All endpoints are prefixed with /actions/headcount/api/.
Authentication
Endpoints use one of two auth modes:
- Session — the request rides on a logged-in Craft user’s session cookie. Best for first-party templates and SPAs.
- API key — pass a Headcount-issued key in the
X-Headcount-Api-Keyheader. Best for server-to-server, mobile apps, and external integrations.
Issue and revoke keys from Headcount → Settings.
The ?apiKey= query parameter was removed in 5.2.0, because it left keys in web server logs, browser history and outbound Referer headers. POST /checkout also requires a CSRF token from 5.2.0 — it is session-authenticated, so front-end JS must send CRAFT_CSRF_TOKEN or the X-CSRF-Token header.
Endpoints
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /plans | Public | List all enabled plans |
| GET | /plan?handle=xxx | Public | Get a specific plan |
| GET | /subscriptions | Session / API key | List the current user’s subscriptions |
| GET | /subscription?id=xxx | Session / API key | Get a specific subscription |
| POST | /checkout | Session | Create a checkout session |
| GET | /portal | Session | Get a Stripe Customer Portal URL |
| GET | /member | Session / API key | Get current member info with subscriptions |
Example: List Plans
curl https://yoursite.com/actions/headcount/api/plans
Alongside the usual name, price and billing interval, each plan carries its term shape. seasonStart and seasonEnd describe the season currently being sold; currentPrice is what a member would actually be charged if they joined right now, which is lower than price once a pro-rata season is under way:
{
"plans": [
{
"handle": "club-membership",
"price": 120,
"currency": "GBP",
"termType": "fixed",
"seasonStart": "2026-07-01T00:00:00+01:00",
"seasonEnd": "2027-06-30T23:59:59+01:00",
"currentPrice": 90
}
]
}
termType is recurring or fixed; the season fields are null for recurring plans.
Example: Get Member Info
curl -H 'X-Headcount-Api-Key: your-key' \
https://yoursite.com/actions/headcount/api/member
Example: Create Checkout Session
curl -X POST \
-H 'X-CSRF-Token: ...' \
-d 'planHandle=pro-monthly' \
https://yoursite.com/actions/headcount/api/checkout
The response includes checkoutUrl and sessionId — redirect the user to checkoutUrl to complete payment. A season plan whose one-off window has ended answers 410 Gone.
Wallet Card Verification
The QR code on a wallet card points at a site URL rather than an API endpoint, so a shop assistant’s phone camera can open it directly. Requesting it with Accept: application/json returns the same answer as JSON, for building a scanner of your own:
curl -H 'Accept: application/json' \
'https://yoursite.com/headcount/wallet/verify?serial=...&token=...'
{
"valid": true,
"memberName": "Sam Okafor",
"planName": "Club Membership",
"expiryDate": "2027-06-30T23:59:59+01:00"
}
Both parameters come from the card itself; a wrong or missing token answers valid: false rather than an error, so a caller probing serial numbers learns nothing.
Webhook Endpoints
Inbound payment provider webhooks live at separate URLs (no auth headers, signature-verified):
POST /actions/headcount/webhook/stripePOST /actions/headcount/webhook/paypal
See the Configuration guide for the events Headcount expects.