Skip to navigation

Seat-based credits

How to grant credits that scale with the number of seats a customer bought, whether they are pooled across the customer or owned by the individual seat, and how to see whose usage reserved which part of a shared pool.


Why this matters

A plan sold per seat often includes credits per seat: every editor comes with 500 AI credits, every workstation with 10 GB of processing. The grant is not a property of the customer, it is a property of the seat count, and that count changes whenever the customer adds or removes people.

Expressing that with a fixed grant means editing the credit quantity on the pricing every time the seat count moves, per customer, which is exactly the kind of manual step that produces a customer who was charged for twelve seats and given credits for eight.

A seat-scaled grant derives the quantity from the seat count the seat fee was already priced with, so the two can never disagree.

How it works

A wallet-grant rule on a pricing item config carries a source field that declares what its quantity is derived from:

sourceQuantityWhere it is allowed
FIXEDExactly what you configuredAny pricing item. This is the default, and what every existing rule does
SEAT_POOLConfigured quantity × the purchased seat countOnly on a PER_SEAT pricing item
PER_SEATConfigured quantity, prorated per seat assignmentOnly on a PER_SEAT pricing item

SEAT_POOL grants the multiplied quantity into the customer’s shared wallet as one grant per rule per pricing period, never one grant per seat. Where the pricing period is shorter than the billing period, one invoice carries a grant for each period it covers; see when the grant is created. Scaling multiplies the quantity on a single grant; it does not fan out into a row per person. Any seat can spend any credit in the resulting pool.

Per-seat pricing item12 seats Wallet grant rule500 credits, SEAT_POOL One grant of 6,000 creditsinto the customer's wallet

Credits owned by an individual seat, where an idle seat’s credits cannot be spent by anyone else, are a different model with the same starting point. That is PER_SEAT, described in Credits owned by one seat.

Where the seat count comes from

This is how SEAT_POOL finds its multiplier. PER_SEAT uses no seat count at all: it grants against the seat assignments themselves, so the two sections below apply to a pooled grant only.

The seat count is resolved per pricing item, in this order:

  1. The charge data on the pricing item summary. This is the authoritative source: it is the very seat count the seat fee was priced with for that sub-period, including any mid-period split.
  2. seats_values on the pricing plan schedule, for that pricing item config, where the summary carries no charge data.
  3. default_seats_value on the pricing item config, as the last fallback.

Because the first source is the priced seat count rather than a separately configured number, the credits a customer receives always match the seats they were billed for, including a period in which the count changed.

Seats added mid-period

Adding seats part-way through a billing period pro-rates the seat fee. The credits follow: a top-up grant is issued for the added seats, using the same day-count ratio the fee used, so a customer who doubles their seats halfway through a month receives half a period’s credits for the new ones.

Nothing is issued when seats are added exactly on cycle, because the period’s main grant is already calculated with the new count, and nothing is issued when the seat count goes down. Credits already granted are not clawed back; the next period is simply granted at the lower count.

Configure a pooled grant

Set source to SEAT_POOL on the wallet grant of a per-seat pricing item’s config, on a pricing plan version or directly on a schedule. The rest of the rule, the wallet type, the quantity and the expiry policy, is unchanged.

Pricing item config granting 500 credits per seat per period
{
"wallet_grants": [
{
"wallet_type_id": "wtyp_...",
"source": "SEAT_POOL",
"credits_grant": {
"credits": { "quantity": "500", "credit_type_id": "ctyp_..." }
},
"expiry_policy": {
"expiry_period": { "type": "MONTH", "value": 1 }
}
}
]
}

On a per-seat item with twelve purchased seats, that rule grants 6,000 credits for the period.

Issued grants carry the same source, read-only, so a grant that was seat-scaled stays identifiable after the fact rather than looking like a fixed grant of an unexplained size.

Validation on wallet_grants.source rejects three things:

  • SEAT_POOL on anything but a PER_SEAT pricing item. Seat counts are keyed by pricing item, not by plan, so there is no count to resolve for another model type. The error names the model type it found.
  • SEAT_POOL combined with credits_grant.conversion. A conversion already derives the grant from the paid amount, which on a per-seat item is seats × unit price, so combining the two would apply the seat count twice.
  • SEAT_POOL combined with amount_grant.percentage, for the same reason.

Via Desk

The grant source is set on the wallet grant inside a pricing, next to the credit quantity. A seat-scaled grant shows the seat count it resolved against, so the pricing screen states what the customer will actually receive rather than the per-seat multiplier alone.

Credits owned by one seat

SEAT_POOL answers “how many credits does this customer get”. PER_SEAT answers “how many does this person get”. Every seat assignment active during the period receives a grant of its own. Only the child customer occupying that seat can spend it, so an idle seat’s credits stay idle rather than being absorbed by a colleague.

Per-seat pricing item3 seats assigned Wallet grant rule500 credits, PER_SEAT 500 creditsheld for seat A 500 creditsheld for seat B 250 creditsseat C, assigned mid-period

A restricted grant is only eligible when the usage being priced belongs to the customer that holds it. Grants from a FIXED or SEAT_POOL rule carry no restriction and stay available to everyone. Usage that cannot be attributed to a seat draws on unrestricted grants only. A personal allowance and a shared pool can therefore live in the same wallet: a seat spends its own grant and the shared one, and nobody else can reach what it holds.

Proration

The quantity is prorated by how much of the period the assignment covers, using the same ratio a mid-period seat fee uses. A seat assigned halfway through a monthly period is granted half the configured quantity. On a handover, the outgoing occupant keeps the part of the allowance their time covered, and the incoming one is granted the remainder.

Over-granting

Credits follow the assignments, and assignments are not checked against the seats the customer paid for. A period with a handover, or with more children assigned than seats bought, therefore grants more than the paid seats account for. That excess is reported rather than withheld. Capping it would make one seat’s allowance depend on what another seat did, which is the behaviour PER_SEAT exists to avoid.

The excess is judged at issuance and read back in three places:

WhereField
On the wallet grantover_granted
On the wallet balance statusover_granted, for the wallet as a whole
On each entry of customer_detailsover_granted, beside that customer’s own balance

over_granted is a reporting figure, not a block. The credits are spendable. Watch it if seats are reassigned often: a seat type handed around within one period can grant a multiple of what was sold.

Configure a seat-owned grant

Set source to PER_SEAT on the wallet grant of a per-seat pricing item’s config:

Pricing item config granting 500 credits to each occupied seat
{
"wallet_grants": [
{
"wallet_type_id": "wtyp_5RmXe2QbtKfYuA9DcNH3P",
"source": "PER_SEAT",
"credits_grant": {
"credits": { "quantity": "500", "credit_type_id": "ctyp_2PnBx7VdtRgLmE4WcQJ8K" }
}
}
]
}

Each grant it issues names the seat it belongs to:

A grant issued to a seat
{
"object_type": "WALLET_GRANT",
"id": "wgra_7KdRa4NvpWzYbT6FgXH2M",
"source": "PER_SEAT",
"seat": {
"customer_id": "cust_5RmXe2QbtKfYuA9DcNH3P",
"product_item_id": "proi_2PnBx7VdtRgLmE4WcQJ8K"
},
"grant_credits": { "quantity": "250", "credit_type_id": "ctyp_7KdRa4NvpWzYbT6FgXH2M" },
"over_granted": { "credits": { "quantity": "50", "credit_type_id": "ctyp_7KdRa4NvpWzYbT6FgXH2M" } }
}

seat.customer_id is the child customer occupying the seat and seat.product_item_id is the per-seat product item that seat is held on, which is the seat type. Both are read-only, and neither is set on a grant from any other source.

Validation on wallet_grants.source adds two rules for PER_SEAT:

  • A seat-owned grant has to be a fixed credits grant. An amount grant cannot be attributed to a single seat. PER_SEAT therefore requires credits_grant.credits, and is otherwise rejected with PER_SEAT requires a fixed credits_grant.credits; a seat-owned amount grant is not supported. As with SEAT_POOL, the pricing item has to be priced PER_SEAT.
  • One rule per wallet type. A wallet type granted from PER_SEAT carries no second grant rule on the same wallet type in that config. Two rules resolve to the same grant, so the second would be dropped silently at issuance. The config is rejected instead.

See whose usage reserved the balance

A pool spent by several children reports one reserved figure, which answers “how much is committed” but not “by whom”. Pass include_customer_details on a balance call to get the reserved balance split per customer:

POST
/v:version/wallets/:resourceIdOrReference/balance
curl -X POST https://test.api.solvimon.com/v1/wallets//balance \
-H "X-API-KEY: <apiKey>" \
-H "Content-Type: application/json" \
-d '{
"balance_at": "2026-10-09T12:00:00Z"
}'

The same flag works on the customer-level call, POST /v1/customers/{id}/wallets/balance, which returns the breakdown for each of that customer’s wallets. It defaults to false, so existing integrations are unaffected.

The split is read from what was recorded on the credit invoice lines when the invoice was built, so it reflects usage that has actually been drafted against the wallet, not a live estimate.

Edge cases

  • The breakdown only covers credit wallets. A wallet denominated in an amount rather than a credit type returns an empty customer_details, because the attribution is recorded per credit type on the invoice line.
  • Only reserved usage is attributed. A reservation exists while the invoice that consumes the credits is still being built. Once that invoice goes FINAL the credits are spent rather than reserved, and the invoice drops out of the breakdown. The breakdown answers “who is about to spend this”, not “who has spent this”.
  • A grant of 0 is rejected. A wallet grant rule with a zero quantity fails validation at configuration time, rather than failing later while the grant is being issued.
  • default_seats_value has to be a number. A seats value is a count, so configuring it as an amount is rejected with default_seats_value: seats value must be a number. A configured count of zero bills no seat line at all.
  • An unresolvable seat count grants nothing. If the summary has no charge data, the schedule has no seats_values for the item and the config has no default_seats_value, there is no count to multiply by and no grant is issued. Set a default_seats_value on the pricing item config if you want a floor.
  • For a pooled grant, seat assignments are not the seat count. The count that scales a SEAT_POOL grant is what the customer paid for, seats_values. Which children occupy those seats is recorded separately as seat assignments, and over-assignment does not increase the credits granted. A PER_SEAT grant is the opposite: it is driven by the assignments, and over-assignment does increase what is granted.