deep-research call costs 5 credits, and 8 when it runs
on gpt-4.1” — without redeploying your client. Requires @tansohq/sdk >=
0.3.0 for typed access; the raw API works from any version.
The weight table is one of two pricing dials: it sets how many credits an
action burns. What one credit costs in money is the
price book, versioned and published the same way.
Resolution
Weights resolve most-specific first, at the event’soccurredAt:
(feature, model)— exact match on the model string(feature, any model)— the feature’s default row1.0— identity, when no row exists
model you send on evaluate and the costInput.model you
send on ingest must be the same string, or the charge silently falls back to
the feature default.
Charge = usageUnits × weight, rounded once before the pool draw-down.
Every response tells you which row matched via weightMatch:
MODEL, FEATURE_DEFAULT, or NONE.
Publishing a tariff
Use the console (Credits → Weights) or the admin API:- One batch, one effective time. All entries share
effectiveFrom, applied in one transaction — there is no window where half the tariff is live. effectiveFrommust be in the future. The settled ledger is never repriced. Publishing “as of yesterday” is rejected with400.- Effective rows are append-only. To change a price, publish a new row
with a later effective time. Only rows that haven’t taken effect yet can be
deleted (
DELETE /weights/{id}, the correction path for a fat-fingered future tariff). - Same-instant collisions return
409. An identical replay of the same batch is an idempotent no-op; a different batch at the same instant is rejected. - Weights are positive, at most 6 decimals, capped at 1,000,000. If you find
yourself needing
0.1, your credit denomination is 10× too large.
effectiveFrom must be in the future, publish your test
tariff a minute out and wait for it — there is no “effective immediately.”
That friction is the feature: production tariffs get the same deliberate
cutover.
Quote, then record
Ask what an action would burn before doing the billable work:occurredAt. A tariff cutover between the two
can change the outcome — by design, so that a burst of pre-cutover checks
can’t lock in pre-cutover prices indefinitely.
Ingestion returns the receipt:
occurredAt — timestamps are clamped to
[now − 48h, now + 5min] for weight resolution, so backfills and webhook
retries keep working; a stale replay can’t reach an old, cheaper rate.
Migrating an existing integration
This is the one place you can double-charge your customers, so read it before publishing your first tariff. If the weight table didn’t exist when you integrated, your client probably sends pre-multiplied units:usageUnits: 5 for a “5-credit action”,
because that was the only lever. If you publish a weight: 5 tariff while
that client is still deployed, the server multiplies your multiplied number —
a 25-credit charge for a 5-credit action.
The safe order:
- Deploy the client change first — send raw usage units (
usageUnits: 1). - Accept the gap. Until the tariff is published, raw units burn at the identity weight of 1.0. That’s an undercharge — annoying, bounded, and safe. The reverse order is an overcharge to real customers.
- Publish the tariff with a future
effectiveFrom.
usageUnits semantics is an account-wide event, not just a
weight-table change. Everything calibrated against the old numbers needs
re-checking in the same cutover: maxUsage caps on plan rules, Stripe meter
prices, and credit grant sizing.