Billing Plan

Change the signed-in user's plan at the payment provider.

ONE route carries both directions, because the SERVER decides what a target tier means. A move to basic is a cancel, because basic is the free tier and no provider sells it. A move to another paid tier is a price change. The route refuses an agent-registration-only tier with 400, because no subscription grants one.

Bearer-authed like the billing routes above: the route reads caller.user_id and acts on that user alone. It also demands billing:write, which the console session mint alone grants, so an agent credential answers 403. It is on the PUBLIC app on purpose. The internal listener authenticates the console process, not the person, so a route there would let a console fault move another user's money.

The route acts on the caller's ONE granting subscription. It refuses when the caller has none, and it refuses when the caller has two or more: a silent pick would change one subscription and leave the other to charge. For a subscription that is not cancelled, a target that equals the subscription's current tier calls the provider not at all, which stops a double proration on a double submit.

A cancelled subscription still grants its tier until ends_at, so it is the caller's one live subscription. The provider refuses a price change on it while the scheduled cancel exists, so the route reads the target differently:

  • The current tier keeps the plan: the route removes the scheduled cancel, and the subscription renews on ends_at.
  • basic calls the provider not at all, because the subscription already ends at ends_at.
  • Another paid tier answers 409 and calls the provider not at all. The user keeps the plan first, and then changes it.

The answer's action is one of unchanged (no provider call), cancelled, changed, or kept. Each request makes at most one provider call.

The route passes the direction it already ranked, and each provider turns that into its own proration flags: an upgrade charges the difference now, so the user pays for the bigger machine at once, and a downgrade credits it onto the next invoice. A downgrade must never produce a charge.

The route writes NO database field. users.plan stays projection-only, and the provider's webhook lands seconds later with the new state. The route also does not wait for an active plan_apply job: the provider owns the money, and a refusal of a paid upgrade is worse than a late apply.

post/me/billing/plan

Request

  • Base URL: https://api.xhostd.com (the document declares no server; this is the origin it was published from)
  • URL: https://api.xhostd.com/me/billing/plan
  • Auth: none declared

Headers

authorizationstring nullable

Request body

tierstring required

Response

Successful Response

unknown required

Changes

No recorded changes to this operation across all 1 revision of this API.