Skip to main content
Updating a plan price changes how it is charged going forward. Depending on what you change, the system either mutates the price in place or creates a new price version. After updating pricing fields, you need to run the price sync workflow to propagate the change to existing subscribers.

The Update Price API

PUT /prices/{id}
All fields are optional. Only include the fields you want to change.

Available Fields

FieldTypeNotes
amountdecimalNew unit price (FIAT prices)
billing_modelstringFLAT_FEE, TIERED, or PACKAGE
tier_modestringVOLUME or SLAB — for TIERED model
tiersarrayNew tier breakpoints (TIERED/FIAT)
transform_quantityobjectPackage billing: divide_by and round
price_unit_amountdecimalNew unit price (CUSTOM price unit)
price_unit_tiersarrayNew tiers (CUSTOM price unit + TIERED)
display_namestringDisplay label shown on invoices
descriptionstringInternal description
lookup_keystringUnique lookup key
metadataobjectKey-value metadata
group_idstringAssign/reassign to a price group; "" clears the group
effective_fromtimestampWhen the new price version takes effect (default: now)

Fields You Cannot Change

These are immutable after a price is created:
FieldWhy
typeCannot switch FIXED ↔ USAGE
currencyCurrency is locked at creation
billing_period / billing_period_countBilling cycle is fixed
billing_cadenceCannot switch recurring ↔ one-time
invoice_cadenceARREAR/ADVANCE is fixed
meter_idThe usage metric cannot be swapped
price_unit_typeCannot switch FIAT ↔ CUSTOM
entity_type / entity_idPrice ownership is fixed

Two Update Paths

Path A — Metadata-only update (in-place)

If you only update display_name, description, lookup_key, metadata, or group_id, the price is updated in place. No new price version is created. No sync is needed.
PUT /prices/price_abc
{
  "display_name": "API Calls (v2)",
  "metadata": { "tier": "enterprise" }
}
Response: the updated price object. Done.

Path B — Pricing field update (new version created)

If you update any pricing field (amount, billing_model, tiers, tier_mode, transform_quantity, price_unit_amount, price_unit_tiers), the system:
  1. Terminates the current price — sets its end_date to effective_from (or now if not provided)
  2. Creates a new price — starts exactly at effective_from, with the updated values
  3. Returns the new price in the response (with a new id)
PUT /prices/price_abc
{
  "amount": "49.99",
  "effective_from": "2026-04-01T00:00:00Z"
}
Response: the new price object (id will be different from the original).
Save the new price id from the response. Existing subscription line items still reference the old price ID. The sync workflow creates new line items referencing the new price.

Syncing the Change to Existing Subscribers

After a pricing field update, existing subscriptions are not automatically updated. You must run the price sync workflow to propagate the new price to all active subscribers on the plan.

Why Manual Sync?

The sync workflow can affect large numbers of subscriptions. Triggering it automatically on every price update would be unpredictable. Doing it explicitly gives you control over timing.

Safe Sync Sequence

Step 1 — Update the price
PUT /prices/{price_id}
{
  "amount": "49.99",
  "effective_from": "2026-04-01T00:00:00Z"
}
Note the plan ID associated with this price (needed for sync). Step 2 — Check for a running sync
POST /workflows/search
{
  "workflow_type": "PriceSyncWorkflow",
  "entity_id": "<plan_id>",
  "workflow_status": "Running"
}
  • If pagination.total > 0 — a sync is already running. Do not trigger another. Poll GET /workflows/{workflow_id}/{run_id} every 30 seconds until status is Completed or Failed, then continue to Step 3.
  • If pagination.total == 0 — proceed to Step 3.
Step 3 — Trigger sync
POST /plans/{plan_id}/sync/subscriptions
Response:
{
  "workflow_id": "PriceSyncWorkflow-plan_xxx",
  "run_id": "run_abc123",
  "message": "price sync workflow started successfully"
}
Step 4 — Poll until complete
GET /workflows/{workflow_id}/{run_id}
Poll every 30 seconds. Terminal states:
StatusAction
CompletedDone — all subscribers updated
FailedCheck error details, fix root cause, re-trigger
Canceled / Terminated / TimedOutRe-trigger sync
Step 5 — Verify (optional) Check the sync summary in the completed workflow response:
{
  "summary": {
    "line_items_found_for_creation": 120,
    "line_items_created": 120,
    "line_items_terminated": 120
  }
}
line_items_terminated = old price line items closed. line_items_created = new price line items added.

What Sync Does After a Price Update

When you update a price, the old price gets an end_date and a new price is created. The sync workflow:
  1. Terminates existing line items that reference the old price (their end_date is set to match the old price’s end_date)
  2. Creates new line items for the new price on all subscriptions that don’t have one yet
Subscriptions with overridden line items (custom pricing) are not affected — their line items point to subscription-scoped prices, not the plan price you just updated.

Using effective_from

effective_from controls when the old price expires and the new one starts.
ScenarioRecommendation
Change takes effect immediatelyOmit effective_from (defaults to now)
Change takes effect at start of next billing periodSet effective_from to the next period start
Scheduled future price changeSet effective_from to the future date, trigger sync in advance
The sync workflow creates new line items with start_date = price.start_date. If you set effective_from: "2026-04-01", the new line items will start on April 1 and the old ones will end on April 1.

Examples

Update a flat fee amount

PUT /prices/price_monthly_base
{
  "amount": "79.00",
  "effective_from": "2026-04-01T00:00:00Z"
}
Then sync: POST /plans/plan_growth/sync/subscriptions

Update usage tiers

PUT /prices/price_api_calls
{
  "billing_model": "TIERED",
  "tier_mode": "VOLUME",
  "tiers": [
    { "up_to": 50000,  "unit_amount": "0.002" },
    { "up_to": 200000, "unit_amount": "0.001" },
    { "up_to": null,   "unit_amount": "0.0005" }
  ]
}

Update metadata only (no sync needed)

PUT /prices/price_api_calls
{
  "display_name": "API Calls — Updated Tiers",
  "metadata": { "updated_by": "billing-team" }
}
No sync required.