/api/v1/subscriptions/{external_ref}
Read and manage the subscription of one user of your app. A subscription is created by a successful mode: "subscription" checkout and belongs to a customer and a price. The full story (trials, reminders, renewals) is in 03.08 — Subscription lifecycle.
| Endpoint | Auth |
|---|---|
GET /api/v1/subscriptions/{external_ref} | HMAC or Firebase ID Token |
PUT /api/v1/subscriptions/{external_ref}/customer | HMAC or Firebase ID Token |
PUT /api/v1/subscriptions/{external_ref}/plan | HMAC only |
With Firebase ID Token, external_ref in the URL must match the token’s own sub claim — any other value returns 403 forbidden. With HMAC, you can use any external_ref that belongs to your app (your backend is trusted to enforce its own authorization before calling this).
GET /api/v1/subscriptions/{external_ref}
Response — success
{
"success": true,
"data": {
"plan_ref": "pro",
"plan_id": "plan_3f9c1a7e5b2d4c8a91e0f6b2",
"price_id": "price_8a1d6c3e9f024b7d5a2c1e90",
"customer_id": "cus_5d2e7b9a1c4f8036ab12de45",
"status": "active",
"current_period_end": "2026-11-03 08:00:00",
"in_trial": false,
"trial_ends_at": null
}
}
| Field | Meaning |
|---|---|
plan_ref | The plan’s code |
plan_id / price_id | The plan and price the subscription currently renews on (null if none is recorded) |
customer_id | The attached customer (cus_…), or null if none is attached yet |
status | The effective status — see below |
current_period_end | End of the paid (or trial) period, UTC YYYY-MM-DD HH:MM:SS. null = lifetime subscription: it never expires |
in_trial | true while the running period is a trial |
trial_ends_at | End of the trial (equals current_period_end during a trial), otherwise null |
status is one of active / canceled / expired. It is computed at read time: a subscription reads as expired as soon as current_period_end has passed, even if no event has been sent yet. Grant access only while status is active. There is no auto-charge; the customer renews by paying again (see 03.08).
Response — no subscription yet
{ "success": false, "error": { "code": "not_found", "message": "No subscription found for this external_ref." } }
HTTP 404. Not necessarily an error condition in your own flow — a user who has never checked out simply has no subscription record.
PUT /api/v1/subscriptions/{external_ref}/customer
Attaches a customer to the subscription, or switches it to another customer. Reminder emails and webhook payloads refer to this customer.
curl -X PUT https://payments.example.com/api/v1/subscriptions/user-42/customer \
-H "X-App-Key: pgk_xxx" -H "X-Timestamp: 1700000000" -H "X-Signature: <HMAC>" \
-H "Content-Type: application/json" \
-d '{ "customer_id": "cus_5d2e7b9a1c4f8036ab12de45" }'
The response is the same object as GET, with the new customer_id. A missing, unknown or foreign customer_id returns 400 customer_not_found (with Firebase ID Token, the customer must also have been created by the same end user). Works with both auth strategies.
PUT /api/v1/subscriptions/{external_ref}/plan
Moves the subscription to another price — for example monthly to yearly. HMAC only: a Firebase ID Token app gets 403 forbidden.
curl -X PUT https://payments.example.com/api/v1/subscriptions/user-42/plan \
-H "X-App-Key: pgk_xxx" -H "X-Timestamp: 1700000000" -H "X-Signature: <HMAC>" \
-H "Content-Type: application/json" \
-d '{ "price_id": "price_1b7e4d0a8c3f4e29d6a5b310" }'
Response: the same object as GET, now showing the new plan_ref, plan_id and price_id.
price_idcan be any price on sale (active, on anactiveplan) — a price of the same plan or of another plan. An unknown, archived or foreign price returns400 price_not_found.- The change takes effect from the next renewal: the running period and what was already paid are untouched, and there is no proration. The next renewal link and reminder emails use the new price.
Errors
| Code | HTTP | When |
|---|---|---|
forbidden | 403 | Firebase ID Token with another user’s external_ref; or PUT .../plan from a Firebase ID Token app |
not_found | 404 | No subscription for this external_ref |
customer_not_found | 400 | PUT .../customer with a missing/unknown customer_id |
price_not_found | 400 | PUT .../plan with a missing/unknown/archived price_id |
See Errors for the full list.