Summary

A wallet denominated in a currency now pays for the invoice. The money comes off the taxable base of the lines it covers, so nothing is taxed twice. Activating a pricing plan schedule can be put behind an approval policy, which is the gate the draft status was built for. A coupon can follow a subscription onto its next schedule instead of ending with the one it started on. A discount can be backdated over billing periods whose invoices are still draft. AvaTax partial exemptions are billed as partial. Pricing plan groups are managed in Desk. The customer portal speaks German and Italian. Also: a webhook that never succeeds is eventually switched off, and workflow executions are shown on the customer and the invoice they ran for.

Pricing

Activating a pricing plan schedule can require approval An approval policy now accepts PRICING_PLAN_SCHEDULE as its source resource type. Where such a policy applies, POST /v1/pricing-plan-schedules/{id}/activate opens the change for review: it creates an approval request, sets the schedule’s approval_status to PENDING_REVIEW and returns the schedule still in DRAFT. The schedule goes live when the request is approved, so a user who may prepare a change can ask for it to be released without releasing it themselves.

Approval runs through the endpoints quotes and invoices already use: POST /v1/approval-requests/{id}/approve, and its decline and cancel siblings. The same approver models apply, by group or ranked. A renegotiation is rarely one schedule, so the three actions also exist in bulk: POST /v1/approval-requests/approve, and its decline and cancel siblings.

Approve every schedule of a change in one request
curl -X POST https://test.api.solvimon.com/v1/approval-requests/approve \
-H "X-API-KEY: <apiKey>" \
-H "Content-Type: application/json" \
-d '{
"resource_ids": ["appr_jwDeeN0tYSY3F7BkeN1v", "appr_QwDeeN0vcYJaBLAc0C29"]
}'

Each request is acted on separately. The response carries a result per resource id, with a status of SUCCEEDED or FAILED and, when it failed, the error that stopped it. One declined approver does not roll back the rest.

Finding what is waiting is a read on the subscription. pending_review_pricing_plan_schedule_ids lists the schedules of a subscription that are pending review, ordered by start_at. pricing_plan_schedule_infos stays what it was and lists only the active ones. Listing approval requests accepts source_ids[] for several sources at once, next to the existing source_id. Sending both is rejected. A platform holds one active approval policy per source type, since these policies carry a single always-approve rule and a second one would repeat it. Schedules also approve in timeline order: a schedule whose predecessor is still draft, or still pending review, cannot be activated ahead of it. See Draft schedules.

A coupon can carry over to the next schedule A coupon is redeemed against a pricing plan schedule, so its discounts live on that schedule. POST /v1/pricing-plan-schedules/{id}/migrate and POST /v1/pricing-plan-schedules/{id}/copy now take coupon_discount_behaviour, which decides what happens to the coupons still running when the schedule is replaced:

Continue the running coupons on the new schedule
{
"pricing_plan_version_id": "ppve_QwDeeN0vcYJaBLAc0C29",
"start_at": "2026-10-01T00:00:00+02:00",
"coupon_discount_behaviour": "CARRY_OVER"
}

NONE is the default: the discounts end with the schedule they were on, as they always have. CARRY_OVER continues them on the new schedule for the time the coupon has left. The end date is recalculated from where the coupon originally started, so a three-month coupon that has run for two carries its last month onto the new schedule. The coupon itself is not redeemed a second time. See Coupons.

A discount can be backdated while its invoices are still draft A discount can now start on any billing period whose invoice has not been issued yet. The lock that protects billed periods follows the last FINAL invoice, so an invoice still sitting in DRAFT, on hold and never sent, leaves its period open to an ad hoc discount agreed before the invoice goes out. A period already invoiced for real stays closed. The same rule governs removing a discount clause that has already started.

A charge on demand started from a payment carries its quantity Charging an on-demand item through a payment authorisation states the quantity and the amount on the payment context itself. Each item carries the same two optional fields the direct request has, so the flow charges a one-off item priced FLAT for the units ordered and a flexible item for the amount agreed, alongside the FIXED items it already handled:

A pricing item on the payment context
{
"pricing_items": [
{ "pricing_item_id": "prii_5RmXe2QbtKfYuA9DcNH3P", "units": { "number": "2" } },
{ "pricing_item_id": "prii_2PnBx7VdtRgLmE4WcQJ8K", "flexible_amount": { "quantity": "50.00", "currency": "EUR" } }
]
}

Both fields are validated when the payment context is created, so a bad quantity reaches the caller instead of surfacing after the customer has paid. Payment contexts persisted in the earlier shape are still read.

Wallets and credits

A wallet denominated in a currency pays for the invoice A CREDIT wallet type funded with an amount holds money rather than credits. Such a wallet now pays for the invoice lines it is allowed to pay for, and the customer is invoiced for the rest.

The wallet holds a net amount, because tax was already charged on the top-up that funded it. So the money comes off the net side of the line, and the covered part carries no tax. The rest of the line is charged and taxed as normal. A 30.00 EUR line at 21%, against a 10.00 EUR balance, invoices 20.00 plus 4.20:

NetVATTotal
Line as priced30.006.3036.30
Covered from the wallet-10.000.00-10.00
Charged20.004.2024.20

Coverage is always split, never skipped. A balance that is short covers less of the line and the remainder is charged. Only lines whose product tax category matches the wallet type’s tax category are in scope; the others are untouched. The deduction is applied last: after included volume, after credits, after discounts and after minimum commitment, on the net amount those leave behind, and therefore before tax. A customer holding both kinds of wallet spends credits first, because credits reduce volume and the money wallet reduces money.

What each wallet paid is reported per charge in a new wallet_balances list, with the wallet named beside its own figures:

What one wallet paid for a charge
"wallet_balances": [
{
"wallet_id": "wall_5RmXe2QbtKfYuA9DcNH3P",
"used_balance": { "amount": { "quantity": "10.00", "currency": "EUR" } },
"left_balance": { "amount": { "quantity": "0.00", "currency": "EUR" } },
"available_balance": { "amount": { "quantity": "10.00", "currency": "EUR" } }
}
]

The list carries an entry for every wallet that paid, so a charge settled by a credits wallet and a money wallet reports each one’s figures separately. It replaces wallet_balance_details, which holds one wallet at a time and only credits. See the deprecations below, and Credit types & wallets.

A grant into such a wallet has to be in the wallet’s own currency. A fixed grant in another currency is rejected with wallet type <reference> is denominated in EUR, but the grant is in USD. A grant that takes a percentage of pricing in another currency is rejected too: there is no conversion, so it names the fixed grant or the other wallet type as the way out. A wallet type from before a default currency was mandatory carries none, and nothing can be granted into it until it has one.

Customers

A seat assignment holds its resources open Seat assignments record which child customer occupies which seat, and per-seat credits are attributed against them. Three resources are now guarded while a live assignment references them: the child customer in the seat, the parent customer the seat is held under, and the per-seat product item that defines the seat type. Archiving a child customer who still holds a live seat is therefore rejected, so a seat cannot stay occupied by a customer that no longer exists operationally. The rejection names the assignment that blocks it and its status. End the assignment first, then change the status. See Seat-based credits.

Fixes

bug-fix
  • A billing email can be removed. PATCH /v1/customers/{id} with {"email": null} came back as INVALID_FIELD, while sending an empty string cleared it. null, "" and whitespace all mean no email now.

Invoices

AvaTax partial exemptions are billed as partial Some jurisdictions tax part of a line and exempt the rest. Texas software is the common case: 8.25% applies to 80% of the base and the remaining 20% is exempt. The taxable fraction AvaTax returns is now carried on the rate and applied. The rates are calculated on the taxable part of the base, and the exempt remainder becomes a single 0% summary, so the invoice and the tax filing both reflect the split. The example above produces four tax lines: three on the 80% base, one exempt line for the 20%. The exempt part is shown once, not per jurisdiction. A fully taxable or fully exempt line is unchanged. An invoice issued earlier keeps the tax it was issued with; refreshing it applies the split. See Tax calculation integrations.

Fixes

bug-fix
  • Custom fields survive an invoice refresh. A CUSTOM_FIELD refresh updated the “show on invoice” fields in the response and in the regenerated PDF, but never wrote them to the invoice. The next GET returned the values from before the refresh.

Workflows

Filter and expand the resources a workflow ran against POST /v1/workflow-action-executions/search now filters on pricing_plan_subscription_id and wallet_id, alongside the invoice, customer, payment, action, workflow, trigger and status filters it already had. The triggers expand what they point at: workflow_id on a workflow trigger, pricing_plan_subscription_id and pricing_plan_schedule_id on a subscription trigger, wallet_id on a wallet trigger, and payment_id on a payment trigger. An execution row expands its workflow_action_id. The execution log can therefore be read in one call, with the resources each row points at inline.

A condition on the invoice’s billing entity invoice.billing_entity.id joins the fields a workflow condition can test, on invoice workflows. A platform billing from several entities can give each one its own automation, rather than running one workflow and filtering afterwards.

Workflow executions are shown where the work happened The execution table now also sits on the customer screen and on the invoice screen, listing the executions for that resource, and on the workflow itself. So “what did the automation do for this invoice” is answered on the invoice. A row opens a panel with the details of that execution. The full log remains a page of its own, as “All workflow executions”.

Fixes

bug-fix
  • The integration is required when configuring an EXPORT_TO_ERP action. The field could be left empty, and the action was saved with nothing to export through.

Integrations

A webhook that never succeeds is switched off A webhook is now disabled automatically after 20 consecutive failed deliveries. disabled_reason on the webhook says why, so a webhook switched off by Solvimon is told apart from one switched off by you. An endpoint that has gone for good therefore drops out of the delivery queue, rather than being retried on a backoff that reaches several hours and never ends. A webhook switched off this way cannot be test-fired or retried out of the penalty box until it is reactivated, and reactivating it clears the reason. See Webhooks.

Customer portal

German and Italian The customer portal and hosted checkout are available in German and Italian, next to English and Dutch. The language switcher is a standard part of both now, rather than something to be enabled. The starting language follows the visitor’s own choice first, then the locale on the customer record. Geolocation is the last guess, because where the browser is does not say who the customer is. A regional variant falls back to its language, so de-AT reads German and nl-BE reads Dutch.

The pay-invoice page runs on the checkout components Paying an invoice from the portal and paying through the hosted checkout now run on the same components, so a payment behaves the same wherever the customer starts it. The invoice amount travels with it to the payment method options, so the methods offered are the ones that can settle the invoice’s currency.

Payments

Restrict the payment methods an Adyen acceptor offers allowed_payment_methods narrows what a payment acceptor of type PAYMENT_GATEWAY on Adyen offers to a chosen set, so one billing entity can accept cards while another accepts direct debit as well. Desk offers the methods enabled on the selected Adyen integration, for integrations Solvimon owns. Leaving it empty offers every method that integration has enabled. See Payment options.

Desk

Pricing plan groups are managed in Desk A pricing plan group is what lets a subscription move between related plans. Plan groups are now a tab beside pricing plans, so a group can be built and maintained without going through the API. A group is created with its name, the plans in it and its portal settings. The plans are ordered by dragging them, lowest tier first, and that order is the upgrade path. Upgrades, downgrades and cancellations each take their own timing: immediately, immediately with pro-rated fees, from the next billing period, or not allowed. A toggle decides whether customers may move between these plans from the self-serve portal themselves. Groups are deprecated and reactivated from the same context menu as the rest of the catalog.

Fixes

bug-fix
  • A user is created with a role. The form allowed a user with none, who then existed without being able to do anything. It preselects Read only.
  • Clearing the end date of a schedule clears it. An absent end_at means “keep the source schedule’s end date” and only an explicit null means “no end date”. Emptying the field dropped the key altogether, so the old end date was kept, usually in the past.
  • Extending an active schedule past a draft that follows it no longer fails with No adjacent schedule found but flag to adjust the NEXT schedule was passed. The save asked to shift a neighbour without checking whether that neighbour was a draft.
  • Archived payment methods are hidden on the customer screen, so what is listed is what can actually be charged.
  • Revenue items on a pricing plan version sit evenly, without the gap that appeared between two of them.

Deprecated API fields

The following fields are now deprecated. They continue to work for backwards compatibility, but we recommend migrating to the replacements.

  • wallet_balance_details (pricing item summary price details): deprecated in favour of wallet_balances. The block holds one wallet at a time, so a charge paid by two wallets reports the figures of whichever paid last, and it can only express credits.
  • used_wallet_credits, left_wallet_credits, available_wallet_credits (pricing item summary wallet balance details): deprecated in favour of the used_balance, left_balance and available_balance of the matching entry in wallet_balances, which carry either credits or an amount.