Skip to content

Commit bd97b3e

Browse files
committed
improvement(billing): replace daily refresh credits with weekly refresh
1 parent d86fdc1 commit bd97b3e

22 files changed

Lines changed: 337 additions & 263 deletions

File tree

apps/docs/content/docs/en/platform/costs.mdx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -331,27 +331,27 @@ Each voice session is billed when it starts. In deployed chat voice mode, each c
331331

332332
Sim has two paid plan tiers - **Pro** and **Max**. Either can be used individually or with a team. Team plans pool credits across all seats in the organization.
333333

334-
| Plan | Price | Credits Included | Daily Refresh |
335-
|------|-------|------------------|---------------|
334+
| Plan | Price | Credits Included | Weekly Refresh |
335+
|------|-------|------------------|----------------|
336336
| **Community** | $0 | 1,000 (one-time) | - |
337-
| **Pro** | $25/mo | 6,000/mo | +50/day |
338-
| **Max** | $100/mo | 25,000/mo | +200/day |
337+
| **Pro** | $25/mo | 6,000/mo | +2,000/week |
338+
| **Max** | $100/mo | 25,000/mo | +4,000/week |
339339
| **Enterprise** | Custom | Custom | - |
340340

341341
To use Pro or Max with a team, select **Get For Team** in subscription settings and choose the tier and number of seats. Credits are pooled across the organization at the per-seat rate (e.g. Max for Teams with 3 seats = 75,000 credits/mo pooled).
342342

343343
Internal organization members use seats and contribute to the team's pooled credit allocation. External workspace members do not join your organization, do not appear in the organization roster, and do not count toward your seat total.
344344

345-
### Daily Refresh Credits
345+
### Weekly Refresh Credits
346346

347-
Paid plans include a small daily credit allowance that does not count toward your plan limit. Each day, usage up to the daily refresh amount is excluded from billable usage. This allowance resets every 24 hours and does not carry over - use it or lose it.
347+
Paid plans include a weekly credit allowance that does not count toward your plan limit. Each week, usage up to the weekly refresh amount is excluded from billable usage. This allowance resets every 7 days from your billing period start and does not carry over - use it or lose it.
348348

349-
| Plan | Daily Refresh |
350-
|------|---------------|
351-
| **Pro** | 50 credits/day ($0.25) |
352-
| **Max** | 200 credits/day ($1.00) |
349+
| Plan | Weekly Refresh |
350+
|------|----------------|
351+
| **Pro** | 2,000 credits/week ($10.00) |
352+
| **Max** | 4,000 credits/week ($20.00) |
353353

354-
For team plans, the daily refresh scales with seats (e.g. Max for Teams with 3 seats = 600 credits/day).
354+
For team plans, the weekly refresh scales with seats (e.g. Max for Teams with 3 seats = 12,000 credits/week).
355355

356356
### Annual Billing
357357

@@ -573,7 +573,7 @@ import { FAQ } from '@/components/ui/faq'
573573
<FAQ items={[
574574
{ question: "How much does a single workflow run cost?", answer: "Every run incurs a base charge of 1 credit ($0.005). On top of that, any AI model usage is billed based on token consumption. Workflows that do not use AI blocks only pay the base run charge." },
575575
{ question: "What is the credit-to-dollar conversion rate?", answer: "1 credit equals $0.005. All plan limits, usage meters, and billing thresholds in the Sim UI are displayed in credits." },
576-
{ question: "Do unused daily refresh credits carry over?", answer: "No. Daily refresh credits reset every 24 hours and do not accumulate. If you do not use them within the day, they are lost." },
576+
{ question: "Do unused weekly refresh credits carry over?", answer: "No. Weekly refresh credits reset every 7 days and do not accumulate. If you do not use them within the week, they are lost." },
577577
{ question: "What happens when I exceed my plan's credit limit?", answer: "By default, your usage is capped at your plan's included credits and runs will stop. If you enable on-demand billing or manually raise your usage limit in Settings, you can continue running workflows and pay for the overage at the end of the billing period." },
578578
{ question: "How does the 1.1x hosted model multiplier work?", answer: "When you use Sim's hosted API keys (instead of bringing your own), a 1.1x multiplier is applied to the base model pricing for Agent blocks. This covers infrastructure and API management costs. You can avoid this multiplier by using your own API keys via the BYOK feature." },
579579
{ question: "Are there any free options for AI models?", answer: "Yes. If you run local models through Ollama or VLLM, there are no API costs for those model calls. You still pay the base run charge of 1 credit per run." },

apps/sim/app/workspace/[workspaceId]/settings/components/billing/billing.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { formatDate } from '@sim/utils/formatting'
1919
import { useRouter } from 'next/navigation'
2020
import { useSession, useSubscription } from '@/lib/auth/auth-client'
2121
import { ON_DEMAND_UNLIMITED } from '@/lib/billing/constants'
22-
import { CREDIT_MULTIPLIER } from '@/lib/billing/credits/conversion'
22+
import { CREDIT_MULTIPLIER, formatCreditCost } from '@/lib/billing/credits/conversion'
2323
import {
2424
getCoveredUsage,
2525
getIsOnDemandActive,
@@ -30,6 +30,7 @@ import {
3030
getDisplayPlanName,
3131
getPlanTierCredits,
3232
getPlanTierDollars,
33+
getPlanWeeklyRefreshDollars,
3334
isEnterprise,
3435
isFree,
3536
isPaid,
@@ -438,6 +439,9 @@ export function Billing({ scope, organizationId, creditUsageHref }: BillingProps
438439
? organizationBilling?.cancelAtPeriodEnd === true
439440
: subscriptionData?.data?.cancelAtPeriodEnd === true
440441

442+
const weeklyRefreshDollars =
443+
getPlanWeeklyRefreshDollars(subscription.plan) * (subscription.seats || 1)
444+
441445
const invoices = (invoicesData?.invoices ?? []).map((invoice) => ({
442446
id: invoice.id,
443447
date: formatDate(new Date(invoice.created * 1000)),
@@ -579,6 +583,15 @@ export function Billing({ scope, organizationId, creditUsageHref }: BillingProps
579583
</div>
580584
)}
581585

586+
{subscription.isPaid && weeklyRefreshDollars > 0 && (
587+
<div className='flex items-center justify-between'>
588+
<span className='text-[var(--text-body)] text-small'>Weekly refresh</span>
589+
<span className='text-[var(--text-muted)] text-small'>
590+
+{formatCreditCost(weeklyRefreshDollars)}/week
591+
</span>
592+
</div>
593+
)}
594+
582595
<div className='flex items-center justify-between'>
583596
<span className='text-[var(--text-body)] text-small'>Payment method</span>
584597
<Chip

apps/sim/app/workspace/[workspaceId]/upgrade/components/comparison-table/comparison-data.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import { DEFAULT_BILLING_CONCURRENCY_LIMITS } from '@/lib/billing/concurrency-defaults'
2-
import {
3-
CREDIT_TIERS,
4-
CREDITS_PER_DOLLAR,
5-
DAILY_REFRESH_RATE,
6-
DEFAULT_FREE_CREDITS,
7-
} from '@/lib/billing/constants'
2+
import { CREDIT_TIERS, CREDITS_PER_DOLLAR, DEFAULT_FREE_CREDITS } from '@/lib/billing/constants'
83

94
/**
105
* Looks up a credit tier by name, so a column binds to the right tier even if
@@ -22,10 +17,6 @@ const MAX_TIER = tierByName('Max')
2217
/** Formats a credit count with thousands separators (e.g. 25000 → "25,000"). */
2318
const formatCredits = (credits: number): string => credits.toLocaleString('en-US')
2419

25-
/** Daily refresh credits for a plan: 1% of plan dollars/day, in credits. */
26-
const dailyRefreshCredits = (dollars: number): number =>
27-
Math.round(dollars * DAILY_REFRESH_RATE * CREDITS_PER_DOLLAR)
28-
2920
/** A brand icon rendered in a cell instead of a check/em-dash/text. */
3021
export interface CellIcon {
3122
/** Icon identifier resolved to a component by the table renderer. */
@@ -94,11 +85,11 @@ export const COMPARISON_SECTIONS: ComparisonSection[] = [
9485
],
9586
},
9687
{
97-
label: 'Daily refresh',
88+
label: 'Weekly refresh',
9889
values: [
9990
false,
100-
`+${formatCredits(dailyRefreshCredits(PRO_TIER.dollars))}`,
101-
`+${formatCredits(dailyRefreshCredits(MAX_TIER.dollars))}`,
91+
`+${formatCredits(PRO_TIER.weeklyRefreshCredits)}`,
92+
`+${formatCredits(MAX_TIER.weeklyRefreshCredits)}`,
10293
'Custom',
10394
],
10495
},

apps/sim/app/workspace/[workspaceId]/upgrade/components/plan-card/plan-card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface UpgradePlanCardProps {
2222
*/
2323
credits?: string
2424
/**
25-
* Daily refresh allocation shown below the credit amount, e.g. `"+50/day refresh"`.
25+
* Weekly refresh allocation shown below the credit amount, e.g. `"+2,000/week refresh"`.
2626
* Only rendered when {@link UpgradePlanCardProps.credits} is also set.
2727
*/
2828
refresh?: string

apps/sim/app/workspace/[workspaceId]/upgrade/plan-configs.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ import { DEFAULT_BILLING_CONCURRENCY_LIMITS } from '@/lib/billing/concurrency-de
77
export interface PlanCredits {
88
/** Formatted credits string, e.g. `"6,000 credits/mo"`. */
99
credits: string
10-
/** Formatted daily-refresh string, e.g. `"+50/day refresh"`. */
10+
/** Formatted weekly-refresh string, e.g. `"+2,000/week refresh"`. */
1111
refresh: string
1212
}
1313

1414
export const PRO_PLAN_CREDITS: PlanCredits = {
1515
credits: '6,000 credits/mo',
16-
refresh: '+50/day refresh',
16+
refresh: '+2,000/week refresh',
1717
}
1818

1919
export const MAX_PLAN_CREDITS: PlanCredits = {
2020
credits: '25,000 credits/mo',
21-
refresh: '+200/day refresh',
21+
refresh: '+4,000/week refresh',
2222
}
2323

2424
export const ENTERPRISE_PLAN_CREDITS: PlanCredits = {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** Pro plan features shown in billing upgrade emails */
22
export const proFeatures = [
33
{ label: '6,000 credits/month', desc: 'included' },
4-
{ label: '+50 daily refresh', desc: 'credits per day' },
4+
{ label: '+2,000 weekly refresh', desc: 'credits per week' },
55
{ label: '150 runs/min', desc: 'sync executions' },
66
{ label: '50GB storage', desc: 'for files & assets' },
77
] as const

apps/sim/content/library/best-multi-agent-frameworks-2026/index.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ slug: best-multi-agent-frameworks-2026
33
title: 'Best Multi-Agent Frameworks for Production in 2026'
44
description: 'Compare the best multi-agent frameworks for production in 2026 across orchestration, state, observability, licensing, self-hosting, deployment, and pricing.'
55
date: 2026-08-25
6-
updated: 2026-08-25
6+
updated: 2026-08-26
77
authors:
88
- andrew
99
readingTime: 19
@@ -163,8 +163,8 @@ That shared graph also makes agent handoffs, state changes, and business rules e
163163
**As of August 2026, [Sim pricing](https://www.sim.ai/pricing) starts at $0 and uses both per-user plan pricing and usage credits.**
164164

165165
- **Free:** $0 with 1,000 monthly credits.
166-
- **Pro:** $25 per user per month with 6,000 monthly credits and a 50-credit daily refresh.
167-
- **Max:** $100 per user per month with 25,000 monthly credits and a 200-credit daily refresh.
166+
- **Pro:** $25 per user per month with 6,000 monthly credits and a 2,000-credit weekly refresh.
167+
- **Max:** $100 per user per month with 25,000 monthly credits and a 4,000-credit weekly refresh.
168168
- **Enterprise:** Custom pricing and custom credits.
169169

170170
According to the [Sim cost documentation](https://docs.sim.ai/platform/costs), one credit equals $0.005. Each run includes a base charge of one credit, with hosted model cost converted to credits. Sim-hosted models use a 1.1× multiplier. BYOK lets customers pay model providers directly at provider pricing with no Sim markup.

apps/sim/lib/billing/calculations/usage-monitor.test.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ const {
1010
mockGetOrgMemberUsageLimit,
1111
mockGetUserUsageLimit,
1212
mockIsOrganizationBillingBlocked,
13-
mockComputeBillingPeriodUsageWithDailyRefresh,
13+
mockComputeBillingPeriodUsageWithWeeklyRefresh,
1414
} = vi.hoisted(() => ({
1515
mockGetBillingPeriodUsageCost: vi.fn(),
1616
mockGetOrgMemberUsageForBillingPeriod: vi.fn(),
1717
mockGetOrgMemberUsageLimit: vi.fn(),
1818
mockGetUserUsageLimit: vi.fn(),
1919
mockIsOrganizationBillingBlocked: vi.fn(),
20-
mockComputeBillingPeriodUsageWithDailyRefresh: vi.fn(),
20+
mockComputeBillingPeriodUsageWithWeeklyRefresh: vi.fn(),
2121
}))
2222

2323
vi.mock('@/lib/billing/organizations/member-limits', () => ({
@@ -39,8 +39,8 @@ vi.mock('@/lib/billing/core/usage-log', () => ({
3939
getBillingPeriodUsageCost: mockGetBillingPeriodUsageCost,
4040
}))
4141

42-
vi.mock('@/lib/billing/credits/daily-refresh', () => ({
43-
computeBillingPeriodUsageWithDailyRefresh: mockComputeBillingPeriodUsageWithDailyRefresh,
42+
vi.mock('@/lib/billing/credits/weekly-refresh', () => ({
43+
computeBillingPeriodUsageWithWeeklyRefresh: mockComputeBillingPeriodUsageWithWeeklyRefresh,
4444
}))
4545

4646
import {
@@ -64,7 +64,7 @@ describe('checkUsageStatus', () => {
6464
setEnvFlags({ isHosted: true, isBillingEnabled: true })
6565
mockGetUserUsageLimit.mockResolvedValue(500)
6666
mockGetBillingPeriodUsageCost.mockResolvedValue(125)
67-
mockComputeBillingPeriodUsageWithDailyRefresh.mockResolvedValue({
67+
mockComputeBillingPeriodUsageWithWeeklyRefresh.mockResolvedValue({
6868
ledgerUsage: 125,
6969
refreshConsumed: 25,
7070
})
@@ -121,17 +121,17 @@ describe('checkUsageStatus', () => {
121121
scope: 'user',
122122
})
123123

124-
expect(mockComputeBillingPeriodUsageWithDailyRefresh).toHaveBeenCalledWith({
124+
expect(mockComputeBillingPeriodUsageWithWeeklyRefresh).toHaveBeenCalledWith({
125125
billingEntity: { type: 'user', id: 'user-1' },
126126
billingPeriod: { start: periodStart, end: periodEnd },
127127
refreshPeriodStart: periodStart,
128128
refreshPeriodEnd: periodEnd,
129-
planDollars: 20,
129+
weeklyRefreshDollars: 10,
130130
})
131131
expect(mockGetBillingPeriodUsageCost).not.toHaveBeenCalled()
132132
})
133133

134-
it('preserves the paid daily-refresh clamp for negative effective usage', async () => {
134+
it('preserves the paid weekly-refresh clamp for negative effective usage', async () => {
135135
const periodStart = new Date('2026-06-01T00:00:00.000Z')
136136
const periodEnd = new Date('2026-07-01T00:00:00.000Z')
137137
const subscription = {
@@ -142,7 +142,7 @@ describe('checkUsageStatus', () => {
142142
periodStart,
143143
periodEnd,
144144
}
145-
mockComputeBillingPeriodUsageWithDailyRefresh.mockResolvedValueOnce({
145+
mockComputeBillingPeriodUsageWithWeeklyRefresh.mockResolvedValueOnce({
146146
ledgerUsage: -1,
147147
refreshConsumed: 1,
148148
})
@@ -173,7 +173,7 @@ describe('checkUsageStatus', () => {
173173
{ type: 'user', id: 'user-1' },
174174
{ start: periodStart, end: periodEnd }
175175
)
176-
expect(mockComputeBillingPeriodUsageWithDailyRefresh).not.toHaveBeenCalled()
176+
expect(mockComputeBillingPeriodUsageWithWeeklyRefresh).not.toHaveBeenCalled()
177177
})
178178

179179
it('preserves negative ledger-only personal usage', async () => {
@@ -194,7 +194,7 @@ describe('checkUsageStatus', () => {
194194
scope: 'user',
195195
})
196196

197-
expect(mockComputeBillingPeriodUsageWithDailyRefresh).not.toHaveBeenCalled()
197+
expect(mockComputeBillingPeriodUsageWithWeeklyRefresh).not.toHaveBeenCalled()
198198
})
199199

200200
it('combines paid organization ledger usage with entity-scoped refresh — no roster read', async () => {
@@ -208,7 +208,7 @@ describe('checkUsageStatus', () => {
208208
periodStart,
209209
periodEnd,
210210
}
211-
mockComputeBillingPeriodUsageWithDailyRefresh.mockResolvedValue({
211+
mockComputeBillingPeriodUsageWithWeeklyRefresh.mockResolvedValue({
212212
ledgerUsage: 100,
213213
refreshConsumed: 10,
214214
})
@@ -221,7 +221,7 @@ describe('checkUsageStatus', () => {
221221

222222
// Refresh is scoped by the entity stamps alone, so departed members'
223223
// org-attributed rows participate identically to current members'.
224-
expect(mockComputeBillingPeriodUsageWithDailyRefresh).toHaveBeenCalledWith({
224+
expect(mockComputeBillingPeriodUsageWithWeeklyRefresh).toHaveBeenCalledWith({
225225
billingEntity: { type: 'organization', id: 'org-1' },
226226
billingPeriod: expect.objectContaining({
227227
start: periodStart,
@@ -230,7 +230,7 @@ describe('checkUsageStatus', () => {
230230
}),
231231
refreshPeriodStart: periodStart,
232232
refreshPeriodEnd: periodEnd,
233-
planDollars: expect.any(Number),
233+
weeklyRefreshDollars: expect.any(Number),
234234
seats: 2,
235235
})
236236
expect(mockGetBillingPeriodUsageCost).not.toHaveBeenCalled()

apps/sim/lib/billing/calculations/usage-monitor.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ import {
1515
type UsageQueryPeriod,
1616
} from '@/lib/billing/core/usage-log'
1717
import { dollarsToCredits } from '@/lib/billing/credits/conversion'
18-
import { computeBillingPeriodUsageWithDailyRefresh } from '@/lib/billing/credits/daily-refresh'
18+
import { computeBillingPeriodUsageWithWeeklyRefresh } from '@/lib/billing/credits/weekly-refresh'
1919
import {
2020
getOrgMemberUsageForBillingPeriod,
2121
getOrgMemberUsageLimit,
2222
} from '@/lib/billing/organizations/member-limits'
23-
import { getPlanTierDollars, isPaid } from '@/lib/billing/plan-helpers'
23+
import { getPlanWeeklyRefreshDollars, isPaid } from '@/lib/billing/plan-helpers'
2424
import { isOrgScopedSubscription } from '@/lib/billing/subscriptions/utils'
2525
import { isBillingEnabled, isHosted } from '@/lib/core/config/env-flags'
2626

@@ -61,17 +61,17 @@ async function computePooledOrgUsage(
6161
return getBillingPeriodUsageCost({ type: 'organization', id: organizationId }, billingPeriod)
6262
}
6363

64-
const planDollars = getPlanTierDollars(sub.plan)
65-
if (planDollars <= 0) {
64+
const weeklyRefreshDollars = getPlanWeeklyRefreshDollars(sub.plan)
65+
if (weeklyRefreshDollars <= 0) {
6666
return getBillingPeriodUsageCost({ type: 'organization', id: organizationId }, billingPeriod)
6767
}
6868

69-
const { ledgerUsage, refreshConsumed } = await computeBillingPeriodUsageWithDailyRefresh({
69+
const { ledgerUsage, refreshConsumed } = await computeBillingPeriodUsageWithWeeklyRefresh({
7070
billingEntity: { type: 'organization', id: organizationId },
7171
billingPeriod,
7272
refreshPeriodStart: sub.periodStart,
7373
refreshPeriodEnd: sub.periodEnd ?? null,
74-
planDollars,
74+
weeklyRefreshDollars,
7575
seats: sub.seats || 1,
7676
})
7777

@@ -134,28 +134,28 @@ export async function checkUsageStatus(
134134
: defaultBillingPeriod())
135135
let ledgerUsage: number
136136
let refreshConsumed = 0
137-
let appliedDailyRefresh = false
137+
let appliedWeeklyRefresh = false
138138
if (sub && isPaid(sub.plan) && sub.periodStart) {
139-
const planDollars = getPlanTierDollars(sub.plan)
140-
if (planDollars > 0) {
141-
const usage = await computeBillingPeriodUsageWithDailyRefresh({
139+
const weeklyRefreshDollars = getPlanWeeklyRefreshDollars(sub.plan)
140+
if (weeklyRefreshDollars > 0) {
141+
const usage = await computeBillingPeriodUsageWithWeeklyRefresh({
142142
billingEntity: { type: 'user', id: userId },
143143
billingPeriod,
144144
refreshPeriodStart: sub.periodStart,
145145
refreshPeriodEnd: sub.periodEnd ?? null,
146-
planDollars,
146+
weeklyRefreshDollars,
147147
})
148148
ledgerUsage = usage.ledgerUsage
149149
refreshConsumed = usage.refreshConsumed
150-
appliedDailyRefresh = true
150+
appliedWeeklyRefresh = true
151151
} else {
152152
ledgerUsage = await getBillingPeriodUsageCost({ type: 'user', id: userId }, billingPeriod)
153153
}
154154
} else {
155155
ledgerUsage = await getBillingPeriodUsageCost({ type: 'user', id: userId }, billingPeriod)
156156
}
157157
const usageBeforeRefresh = ledgerUsage - refreshConsumed
158-
const currentUsage = appliedDailyRefresh ? Math.max(0, usageBeforeRefresh) : usageBeforeRefresh
158+
const currentUsage = appliedWeeklyRefresh ? Math.max(0, usageBeforeRefresh) : usageBeforeRefresh
159159

160160
return buildUsageData({ currentUsage, limit, scope, organizationId })
161161
} catch (error) {

0 commit comments

Comments
 (0)