Order a plan change
const url = 'https://api.flotera.com/api/v1/subscription/changes';const options = { method: 'POST', headers: { cookie: '__Host-erm_session=<__Host-erm_session>', 'Idempotency-Key': 'example', 'Content-Type': 'application/json' }, body: '{"target_plan":"example","topup_id":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.flotera.com/api/v1/subscription/changes \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: example' \ --cookie __Host-erm_session=<__Host-erm_session> \ --data '{ "target_plan": "example", "topup_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0" }'Outcome depends on the direction computed from the effective prices. Effective dates follow the TARGET plan’s change policy (admin-set, frozen into the order when it is placed):
upgradewithcovered— activated immediately (status=applied), entitlements now, money from the SAME UTC day by default (upgrade_effective=next_daymoves billing to the next day while entitlements stay immediate);upgradewithoutcovered— a pending change (status=awaiting_payment); the panel then creates a top-up viaPOST /billing/topupswithpurpose={kind: subscription_change, change_id}, which links it to the order (topup_id). Activation is decided by the verified balance, never by a browser redirect;downgrade— exactly one scheduled change (status=scheduled) on the first UTC day of the next calendar month by default (downgrade_effective=next_daymoves it to the next UTC day), never gated by balance;lateral— applied immediately.
The response is the ordered SubscriptionChange itself, read back
from storage: 200 when it is already applied, 201 when it is
scheduled or awaiting payment.
A tenant has at most one live pending change. Replacing it requires
If-Match on the subscription version; a second unconditional
request returns 409 change_already_pending.
The rule is re-read when the change fires, not frozen at order
time: if the catalog price moved and a scheduled downgrade would now be
an upgrade, it is rejected (direction_changed) rather than silently
applied, and the tenant is notified.
Denied while frozen (403 account_frozen): leaving for a cheaper plan
looks like an exit but is not one — the debt is unchanged and only a
top-up above zero unfreezes the account (14 §3.6). A change ordered
before the freeze still fires on schedule.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Header Parameters
Section titled “Header Parameters”Idempotency key, 1–128 ASCII, unique per (tenant_id, operation, key) for at least 24 hours. Retrying with the same body replays the stored response; a different body returns 409 idempotency_conflict.
Optimistic concurrency guard on the resource version; mismatched tag returns 412.
Request Bodyrequired
Section titled “Request Bodyrequired”object
Examplegenerated
{ "target_plan": "example", "topup_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"}Responses
Section titled “Responses”Applied immediately (upgrade covered by balance, or lateral).
object
UUID (v7 for new entities; v4 accepted during migration).
Direction at order time. It explains why the change is called a downgrade; it does not decide anything when the change fires — the rule is re-read then.
Internal catalog code. Not the public label — labels are not unique.
RFC 3339 UTC with microsecond precision.
First UTC day the new price applies to.
One day of the target plan at the current fleet size, when gated.
reserve_not_covered — the balance no longer covered the reserve when the change fired; direction_changed — the catalog price moved and an ordered downgrade would now be an upgrade, so it was refused instead of silently costing more.
RFC 3339 UTC with microsecond precision.
RFC 3339 UTC with microsecond precision, or null.
Example
{ "status": "scheduled", "direction": "upgrade", "effective_at": "2026-07-25T10:15:30.123456Z", "requested_at": "2026-07-25T10:15:30.123456Z"}Change scheduled or awaiting payment.
object
UUID (v7 for new entities; v4 accepted during migration).
Direction at order time. It explains why the change is called a downgrade; it does not decide anything when the change fires — the rule is re-read then.
Internal catalog code. Not the public label — labels are not unique.
RFC 3339 UTC with microsecond precision.
First UTC day the new price applies to.
One day of the target plan at the current fleet size, when gated.
reserve_not_covered — the balance no longer covered the reserve when the change fired; direction_changed — the catalog price moved and an ordered downgrade would now be an upgrade, so it was refused instead of silently costing more.
RFC 3339 UTC with microsecond precision.
RFC 3339 UTC with microsecond precision, or null.
Example
{ "status": "scheduled", "direction": "upgrade", "effective_at": "2026-07-25T10:15:30.123456Z", "requested_at": "2026-07-25T10:15:30.123456Z"}Missing/invalid credentials (code=unauthorized).
RFC 9457 problem document with a stable machine code.
object
Stable machine-readable error code (03 §2.4).
UUID (v7 for new entities; v4 accepted during migration).
object
JSON Pointer to the offending field.
Example
{ "type": "https://ermeon.com/problems/validation", "code": "unauthorized"}Frozen account (code=account_frozen), contract-priced target (code=feature_not_entitled) or missing scope (code=forbidden).
RFC 9457 problem document with a stable machine code.
object
Stable machine-readable error code (03 §2.4).
UUID (v7 for new entities; v4 accepted during migration).
object
JSON Pointer to the offending field.
Example
{ "type": "https://ermeon.com/problems/validation", "code": "unauthorized"}Resource does not exist in this tenant. Foreign identifiers also return 404 (anti-enumeration).
RFC 9457 problem document with a stable machine code.
object
Stable machine-readable error code (03 §2.4).
UUID (v7 for new entities; v4 accepted during migration).
object
JSON Pointer to the offending field.
Example
{ "type": "https://ermeon.com/problems/validation", "code": "unauthorized"}A live pending change already exists (code=change_already_pending), the tenant has no plan assignment (code=plan_required), or the fleet exceeds the target plan limit (code=server_limit_reached).
RFC 9457 problem document with a stable machine code.
object
Stable machine-readable error code (03 §2.4).
UUID (v7 for new entities; v4 accepted during migration).
object
JSON Pointer to the offending field.
Example
{ "type": "https://ermeon.com/problems/validation", "code": "unauthorized"}If-Match entity tag does not match the current version.
RFC 9457 problem document with a stable machine code.
object
Stable machine-readable error code (03 §2.4).
UUID (v7 for new entities; v4 accepted during migration).
object
JSON Pointer to the offending field.
Example
{ "type": "https://ermeon.com/problems/validation", "code": "unauthorized"}Request failed validation (code=validation_failed), with per-field errors.
RFC 9457 problem document with a stable machine code.
object
Stable machine-readable error code (03 §2.4).
UUID (v7 for new entities; v4 accepted during migration).
object
JSON Pointer to the offending field.
Example
{ "type": "https://ermeon.com/problems/validation", "code": "unauthorized"}