Summary
Credit grants on a pricing item can now scale with the number of seats purchased. Seat occupancy is tracked as its own resource through the new seat assignments API, and a wallet can show which customer’s usage reserved which part of its balance. Also: a credit-amount wallet’s tax category is now always set, workflow emails include the template variables the legacy integration used to inject, and draft workflows can be deleted.
Pricing
You can now leave product items unpriced One product can back several plans that each charge for a different subset of its items: leave the rest unpriced, and events matching those items produce no charge on that plan. The API always allowed a pricing to skip product items of its product; Desk was what enforced pricing every item. Now you can remove a product item from a pricing on a pricing plan version, a rate card version, or a schedule pricing, which makes filtered product items usable in practice. An unpriced item still renders as an “Unpriced” card with an “Add pricing” action, so leaving one out stays visible rather than silent.
Wallets and credits
Credit grants that scale with seats
A wallet-grant rule on a pricing item config declares what its quantity is derived from through a new source field, so a plan sold per seat can hand out credits per seat without a separate rule per customer. FIXED is the default and grants the quantity exactly as configured, which is what every existing rule does. SEAT_POOL multiplies the configured quantity by the applicable purchased seat count and grants the result into the shared customer wallet as a single grant per rule per billing period, never one grant per seat. The seat count comes from seats_values on the pricing plan schedule for that pricing item, falling back to default_seats_value on the item config when the schedule sets none.
SEAT_POOL is only valid on a PER_SEAT pricing item, since seat counts are scoped to a pricing item rather than to the schedule as a whole; the API rejects it elsewhere with a validation error on wallet_grants. Issued grants carry the same source read-only, so a grant that was seat-scaled is identifiable after the fact. The enum also declares PER_SEAT, for credits owned by an individual seat rather than pooled; it is not implemented yet and is rejected with PER_SEAT is not supported yet. See Seat-based credits for the full configuration, including how the seat count is resolved and how seats added mid-period are topped up.
In Desk, the grant source is configured on the wallet grant in a pricing, and a seat-scaled grant shows the seat count it resolved against.
See whose usage reserved a wallet balance
A shared credit pool spent by several child customers reported one reserved figure with no way to see where it came from. The wallet balance status can now return customer_details, a breakdown of the reserved balance per customer, requested with include_customer_details on the balance call. Each entry carries customer_id and that customer’s share of the reserved balance. The split is recorded when an invoice is built or refreshed, so it reflects the usage that has actually been drafted against the wallet.
A credit-amount wallet type’s tax category is enforced
A CREDIT wallet type funded with an amount carries a tax category and a default currency. The tax category was required at create time but only stored and returned: nothing read it, so it constrained nothing. It is now the declared spending scope of the wallet. Money funded under one tax category must not become spendable under another, so the tax category and the default currency can no longer be changed once wallets exist for that wallet type, and neither can be removed once set. Changing either on a wallet type that has no wallets yet is still allowed, and an attempt to change one under existing wallets is rejected with a validation error on credit.amount_grant.tax_category or credit.amount_grant.default_currency. See Credit types and wallets.
Customers
Seat assignments: record which customer occupies a seat
seats_values on a pricing plan schedule records how many seats are paid for, but nothing recorded which child customer occupies one. A processing-only child was linked to its parent only through parent_resource_ids, so there was no way to exclude a child from being counted as a seat, to distinguish seat types, or to compare occupied seats against paid seats. Seat assignment is now an explicit resource at /v1/seat-assignments, with create, list, get, update and delete:
Occupancy is keyed on the PER_SEAT product item, which is the seat type, rather than on the schedule, because capacity itself is scoped per pricing item. status is derived at read time from the dates: SCHEDULED before start_at, ACTIVE while the seat is occupied, ENDED after end_at. Ending an assignment is a PATCH that sets end_at, which must be strictly after start_at; clearing end_at re-opens the seat. On its own this changes no billing behaviour: it is the record that makes seat-owned credits and occupied-versus-paid reporting possible. Four permissions govern it, SEAT_ASSIGNMENT.VIEW, SEAT_ASSIGNMENT.CREATE, SEAT_ASSIGNMENT.UPDATE and SEAT_ASSIGNMENT.DELETE, with the viewer role rolled into Read Only. See Seat assignments.
Subscriptions
Preview an invoice for a subscription that has only draft schedules A subscription whose schedules are all still drafts could not be previewed: with no active schedule there was nothing to price, so the preview refused rather than showing what the drafts would produce. The preview now includes draft schedules, which is what makes a draft subscription reviewable before anyone activates it.
Draft schedules behave as one unit on a draft subscription While a subscription is itself a draft, every schedule on it is a draft too, so the per-schedule draft chip, the per-schedule activate action and the “Create as draft” toggles added noise around a decision that is not yours to make yet. On a draft subscription these are gone: schedules are created as drafts and all of them are activated together when you activate the subscription, from a single button. On an active subscription new schedules are still created as drafts, and the existing per-schedule controls are unchanged.
Quotes
Sales representatives can add schedules to a draft quote version
Creating a pricing plan schedule required PRICING_PLAN_SCHEDULE.CREATE, which is the permission that governs changing live subscriptions. A sales representative building a quote therefore needed a permission far broader than the work in front of them. Schedule creation now checks which kind of subscription it targets: a quote subscription requires QUOTE_PRICING_PLAN_SCHEDULE.CREATE and is accepted only while the quote version is DRAFT, and anything else still requires PRICING_PLAN_SCHEDULE.CREATE. Adding a schedule to a quote version that has left draft is rejected with a validation error on quote_version_id, since a change to agreed terms belongs in the upgrade quote flow rather than in an edit.
Custom fields carry over to a new quote version Creating a new version of a quote sends the custom fields of the version it came from. The link a CRM establishes through a custom field, an opportunity id in particular, survived into the subscription when a quote ran straight through, but was lost as soon as the quote was rejected or returned to draft and continued from a new version. The subscription created from that version now carries the link. See the Salesforce quotes integration.
Invoices
A new invoice PDF renderer, in preview Invoice PDFs can be produced by the same rendering service that already produces quote PDFs, which brings invoices onto one templating and localisation stack: the layout is built from the same component library as the rest of the product, and the invoice template ships translations for the languages the customer portal supports. It is opt-in while it is being rolled out. Contact your account manager to have it enabled for your platform.
An auto rescue stops when the invoice is paid another way
An invoice payment refused for insufficient funds schedules an automatic rescue with the payment provider. If the platform then settled the invoice through POST /v1/invoices/{id}/pay, the rescue still fired later and charged the customer a second time, leaving a refund to be issued. A successful invoice payment now cancels any pending auto rescue for that invoice, and a second auto rescue is not started for an invoice that already has one active. See dunning and payment retries.
Workflows
Delete a draft workflow
A workflow in DRAFT can be deleted, in line with the other draft resources: subscriptions, customers and pricing plans. A workflow that was created by mistake, or that was superseded before it ever ran, no longer has to be left in place. Deletion is governed by the new WORKFLOW.DELETE permission, and only draft workflows are deletable; an active one still has to be deactivated rather than removed, so no execution history disappears.
Expand the trigger on an action execution
GET /v1/workflow-action-executions/{id} and POST /v1/workflow-action-executions/search accept expand[]=workflow_trigger_id, which inlines the full workflow_trigger that produced the row. Building a list of executions with the resource each one acted on was a second call per row; it is now part of the search response. Successful email actions also record a more useful reason, naming the invoice and the message id the provider accepted, so a delivery can be traced from the execution log into the provider without correlating timestamps.
More template variables on the send-email action
The native send-email action now passes invoice_type, open_amount, customer_name, customer_type, credit_note and original_invoice_number to the template alongside the variables it already sent. Templates written against the legacy Paragon-based Mailgun integration relied on these, and rendered them as empty values after the migration to native workflows. Migrated templates that use them render correctly without being rewritten.
Action execution details in Desk A workflow action execution opens into a details panel showing what the action did, the trigger it came from and the outcome it recorded, rather than only the row in the executions table.
Customer portal
Fixes
- The Pay button is visible on the invoice list in the portal. It worked, but was not rendered, so paying from the list was only discoverable by accident; the overview page was unaffected.
- Embedded components receive your platform’s primary colour as their brand colour, so an embedded balance or subscription view matches the surrounding page instead of falling back to a default palette.
Desk
Consistent metric selection on products The billing metric selector on a product item follows the same pattern as the rest of the side panel, and the meter filter, tiering metric and unit names moved into a context menu next to it. Choosing what a usage-based item measures looked different from every other field around it, and each of the related settings used its own control.
The integration reference explained where it is used The reference on an integration is what makes a multi-account setup possible: several integrations of the same type on one platform, told apart by their reference. The dialog now explains what the reference is for and what to put in it, rather than presenting an unexplained field.