Change subscription item
const url = 'http://localhost:8080/v1/subscriptions/example/items/example';const options = { method: 'PATCH', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"quantity":1,"new_plan_id":"example","immediate":false}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request PATCH \ --url http://localhost:8080/v1/subscriptions/example/items/example \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "quantity": 1, "new_plan_id": "example", "immediate": false }'Changes the quantity or the plan of one item. Send exactly one of
quantity or new_plan_id. The subscription must be active.
- Quantity. Takes effect now.
- Plan,
immediate: true. Takes effect now. If the new plan has a different billing interval, the current period ends now and a new period starts on the new plan. - Plan,
immediate: false(default). Scheduled for the end of the current period. The item shows it inpending_plan_idandpending_plan_effective_at. Cancel it withDELETE /v1/subscriptions/{id}/items/{itemID}/pending-change.
For a change that takes effect now on a plan billed in advance,
Hardcount settles the rest of the period at once. An increase is
invoiced. A decrease is credited to the customer’s balance with a
credit note, or reduces the amount due when the period’s invoice
is still unpaid. proration in the response describes the result.
A plan billed in arrears is settled at the end of the period.
Changes to one subscription’s items run one at a time, and each is settled from the item as the change before it left it. A request that repeats a change that already finished finds nothing to change and gets 422.
Sends subscription.item.updated, or
subscription.pending_change.scheduled for a scheduled plan
change.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Header Parameters
Section titled “Header Parameters”Makes the call safe to retry. Send a unique value (a UUID works) and
the same value on every retry. For 24 hours, a repeat with the same
key, method, path and body gets the first response again, with an
Idempotent-Replayed: true header, instead of running again. The
error cases are in docs/api-surface.md. The usage routes do not take
it: each usage event carries its own idempotency_key.
Request Bodyrequired
Section titled “Request Bodyrequired”object
The new quantity. Must differ from the current one.
The plan to move the item to. Must differ from the current one and use the same base-fee billing timing (in advance or in arrears).
Apply a plan change now instead of at the end of the period. Ignored for quantity changes.
Responses
Section titled “Responses”The changed item and when the change takes effect.
object
One priced line of a subscription. It pairs a plan with a quantity.
object
The item’s stored metadata. Absent when empty.
object
Plan the item moves to at pending_plan_effective_at. Absent when no change is scheduled.
When the plan was last changed immediately. Absent until the first such change.
When the change takes effect. For a scheduled plan change, the end of the current period.
What Hardcount did to settle the rest of the period for an item change.
object
Share of the full billing cycle that was left, from 0 to 1.
Gross amount, tax included: the invoice total (invoice), or
the whole credit for unused time (credit and adjustment).
credited_to_balance_cents, reduced_open_invoices_cents and
held_on_draft_invoices_cents say where the credit went.
invoice: a proration invoice was created. credit: some of
the credit went to the customer’s balance, or no invoice had
room for any of it (amount_cents is then 0). adjustment:
none of it went to the balance.
The proration invoice. Present when type is invoice.
The part of the credit added to the customer’s balance, tax
included. On a paid invoice, all of its part. On an open invoice
paid partly with credits, the part above what it still asks for:
those credits go back to the balance. A part on an invoice whose
payment is still processing counts here; if that payment fails,
it reduces the invoice instead. 0 when type is invoice.
The part of the credit taken off the amount due on open
invoices, tax included. 0 when type is invoice.
The part of the credit on draft invoices, tax included. It is
held until the draft is finalized, then applied by how the
invoice stands: if paid, to the customer’s balance; if still
open, off what it asks for first, as above. 0 when type is
invoice.
The invoice an immediate change to a plan with another billing interval created for the new period it starts.
object
draft when the invoice was not issued yet: collection is
paused, or its tax could not be calculated yet. Its total may
then change before it is finalized.
The invoice total when the change created it. On a draft
whose tax could not be calculated yet, it has no tax in it.
Example
{ "proration": { "type": "invoice" }, "new_period_invoice": { "status": "draft" }}The body is not valid JSON.
object
object
One of invalid_request_error, authentication_error,
rate_limit_error or api_error. Every error the API
answers has this shape, including an unknown path (404,
code route_not_found) and a method the path does not take
(405, code method_not_allowed, with an Allow header).
The few exceptions are listed in docs/api-surface.md.
The request field the error is about. Sent only when the error
names one. A request body may hold only the fields its
operation lists, spelled and cased as listed: any other
field, or one the server sets (such as id), is refused
with 422 and param naming it. An operation with no request body lists no fields: send
no body, {} or null. The Stripe and Postmark webhooks and
the LiteLLM callback take their sender’s body as it comes.
Example
{ "error": { "type": "invalid_request_error" }}Subscription not found, or the item is not on this subscription.
object
object
One of invalid_request_error, authentication_error,
rate_limit_error or api_error. Every error the API
answers has this shape, including an unknown path (404,
code route_not_found) and a method the path does not take
(405, code method_not_allowed, with an Allow header).
The few exceptions are listed in docs/api-surface.md.
The request field the error is about. Sent only when the error
names one. A request body may hold only the fields its
operation lists, spelled and cased as listed: any other
field, or one the server sets (such as id), is refused
with 422 and param naming it. An operation with no request body lists no fields: send
no body, {} or null. The Stripe and Postmark webhooks and
the LiteLLM callback take their sender’s body as it comes.
Example
{ "error": { "type": "invalid_request_error" }}Code invalid_state: the subscription is not active, the new
plan is archived, its meters (the meters it has a price for)
overlap another live subscription of the customer, or its
currency differs. Code
unpaid_invoice_blocks_change: an increase would charge against
an unpaid invoice for the current period (a written-off invoice
is unpaid); settle or void it first. Code subscription_period_moved: the billing period
changed while the request ran; read the subscription again and
retry. Code item_changed: a plan change found the
subscription’s items changed by another request that finished
first; read the subscription again and retry. Code
unbilled_period: a billing period ended and could not be
invoiced; retry billing first.
object
object
One of invalid_request_error, authentication_error,
rate_limit_error or api_error. Every error the API
answers has this shape, including an unknown path (404,
code route_not_found) and a method the path does not take
(405, code method_not_allowed, with an Allow header).
The few exceptions are listed in docs/api-surface.md.
The request field the error is about. Sent only when the error
names one. A request body may hold only the fields its
operation lists, spelled and cased as listed: any other
field, or one the server sets (such as id), is refused
with 422 and param naming it. An operation with no request body lists no fields: send
no body, {} or null. The Stripe and Postmark webhooks and
the LiteLLM callback take their sender’s body as it comes.
Example
{ "error": { "type": "invalid_request_error" }}Code validation_error: neither or both of quantity and
new_plan_id are set; the value equals the current one;
quantity is less than 1; the new plan is not found; or the new
plan’s billing timing or interval does not fit the subscription.
Also: the body has an unknown field (param names it; the body must use only the fields listed here).
object
object
One of invalid_request_error, authentication_error,
rate_limit_error or api_error. Every error the API
answers has this shape, including an unknown path (404,
code route_not_found) and a method the path does not take
(405, code method_not_allowed, with an Allow header).
The few exceptions are listed in docs/api-surface.md.
The request field the error is about. Sent only when the error
names one. A request body may hold only the fields its
operation lists, spelled and cased as listed: any other
field, or one the server sets (such as id), is refused
with 422 and param naming it. An operation with no request body lists no fields: send
no body, {} or null. The Stripe and Postmark webhooks and
the LiteLLM callback take their sender’s body as it comes.
Example
{ "error": { "type": "invalid_request_error" }}