AI Workspace plans

A workspace tier is one string on your account, and one config record decides what it costs.

The ladder is eight records with category ai_workspace in PLAN_OBJ (config.json:2206 free, then :2225, :2245, :2265, :2285, :2305, :2325 and :2345). Each one carries id, name, storage, category, price, ai_credits, size, price_id and a features array of seven marketing sentences, plus a member_price_id on the seven paid rungs, and nothing else. No rank, no price_note, no flags, in an object where six other categories carry a rank, thirteen carry flags and one carries a price_note. The tier itself is the field ai_workspace_plan on the account document in xuda_accounts, defaulted to free_ai_workspace when the Stripe customer is first created (stripe_module:2213 and :4823) and stamped with a companion ai_workspace_plan_changed timestamp whenever it moves. Every price, every credit count and every storage label on this page is read straight out of those eight records. The number of tokens a credit buys is not, because it cannot be: the formula is in the repo, the authoritative usage total comes from a CouchDB list function that is not, and the code rescales one to reach the other. So this page says so rather than guessing.

Eight records, $0 to $230
Price per credit, $0.0400 down to $0.0230
No credits-per-token figure, and here is why
A workspace tier is one string on your account, and one config record decides what it costs.
8

ai_workspace tiers in config.json PLAN_OBJ

4 of 8

Tier ids whose number is not the credits they grant

7

Call sites that mint ledger credits, and none is a workspace tier

2

Tiers whose Stripe price id is still a TODO placeholder

A correction

The version of this page you may have read is wrong in four places, and one of them is the headline.

The published doc opens on model routing, and the routing claim does not survive a read of the function it rests on. Three more of its claims do not either. The left column paraphrases what that page carries rather than quoting it; the right column is the source, with the file and the lines. Everything after this block is built on the second column only.

What the published page says
What the source says
Model routingEach step of a build is matched to an appropriate model, so routine work is not billed at top rates.Automatic selection exists in one function, pick_dashboard_model (ai_module:11152-11163), called from one line on the project assistant path, with two outcomes. Its strong tier falls through to gpt-3 because strong_ai_model is absent from config.json, so gpt-4 and gpt-5 are never reached automatically.
GPT-5.5GPT-5.5 is the expensive model the routing keeps you off.GPT-5.5 is not a catalogue name. It is an alias key at config.json:625 pointing at the code gpt-5, whose real model id is gpt-5.6-sol. The alias still lands on a live catalogue entry, so nothing bills at a stale rate, but the name is not a model.
The free tierThe free workspace gives 30 monthly AI credits.Not monthly and not one grant. A 30 credit one-off welcome topup (misc_module:938), plus 1 period credit every 24 hours to accounts still on free_ai_workspace (account_module:9597). Period credits do not roll over; the welcome topup sits until it is spent.
The plan tableA table of six tiers, with credits and storage on each.Six of the eight: 200_ai_workspace and 1500_ai_workspace are absent. The credit numbers are the pre-rework ones against the current storage, so four of its six rows understate credits, and the table carries no price field at all.
The eight records

The eight tiers, exactly as stored.

In price order, straight out of config.json: free $0 for 30 credits and 15 GB, 200_ai_workspace $8 for 200 and 100 GB, 400_ai_workspace $15 for 400 and 500 GB, 600_ai_workspace $25 for 800 and 1 TB, 1500_ai_workspace $45 for 1500 and 2 TB, 2000_ai_workspace $75 for 2500 and 3 TB, 3000_ai_workspace $115 for 4000 and 5 TB, 6000_ai_workspace $230 for 10000 and 10 TB. The seven paid records also carry a member_price_id beside the one shown here; the free record is the one with no member price at all. Two limits belong with the table rather than under it. The storage column is a label: the field code actually reads for entitlement is size, in bytes, and the two can disagree. And these are the repo copies, which are known to diverge from the live master config, so treat every figure as unverified against the running platform.

Monthly price
ai_credits on the record
Storage label
Stripe price id
free_ai_workspace$03015 GBA real price id
200_ai_workspace$8200100 GBprice_LIVE_200_AI_WORKSPACE_TODO
400_ai_workspace$15400500 GBA real price id
600_ai_workspace$258001 TBA real price id
1500_ai_workspace$4515002 TBprice_LIVE_1500_AI_WORKSPACE_TODO
2000_ai_workspace$7525003 TBA real price id
3000_ai_workspace$11540005 TBA real price id
6000_ai_workspace$2301000010 TBA real price id
One record, printed

The key says 600. The record grants 800.

For four of the eight tiers the number in the id is a legacy label rather than the credit grant. 600 grants 800, 2000 grants 2500, 3000 grants 4000 and 6000 grants 10000. Only free, 200, 400 and 1500 still agree with their own key. What happened is visible in the record: the name string was updated to the new number and the key was left alone, and the dashboard PlanCard renders name, so a customer never sees the id at all. Commit 8a9b09673, dated 2026-07-09, did the rework and says so in its own message: two new tiers plus credit bumps on the 600, 2000, 3000 and 6000 rungs. Nothing in code parses a number out of the id, so the mismatch is cosmetic to the runtime and dangerous only to humans writing copy about it.

config.json:2245-2256 . PLAN_OBJ, category ai_workspace
"600_ai_workspace": {
"id": "600_ai_workspace",
"name": "800 AI Credits / 1 TB storage",
"storage": "1 TB",
"category": "ai_workspace",
"price": 25,
"ai_credits": 800,
"size": 1099511627776,
"price_id": "price_... a real Stripe id, elided here",
"member_price_id": "price_... the 10 percent one",
"features": [ seven marketing sentences, collapsed to this line ]
}
the name string was updated in the rework . points at the name line above
the key was not, and 600 is now just a label . points at the ai_credits line above
The same shape repeats at config.json:2265 (2000_ai_workspace, ai_credits 2500), :2285 (3000_ai_workspace, ai_credits 4000) and :2305 (6000_ai_workspace, ai_credits 10000). One more number worth stating while the record is open: 6000 carries a size of 10,737,418,240,000 bytes, and get_effective_entitlements divides size by 1073741824, so the delivered figure is 10,000 GB against a label reading 10 TB.
Price per credit

Divide each record by itself and the bulk discount is 42.5 percent, with a flat step in the middle.

Dividing a price by the number in the tier id gives a curve that does not exist. Dividing it by the ai_credits on the same record gives the real one, and it is flatter than the ids suggest: from $0.0400 per credit at the cheapest paid rung to $0.0230 at the largest, a total of 42.5 percent off, spread over seven rungs rather than concentrated at the top.

  • The seven paid rungs, price divided by the ai_credits on the same record: $0.0400, $0.0375, $0.03125, $0.0300, $0.0300, $0.02875 and $0.0230.
  • The curve is monotonic but flat in one place. $45 and $75 cost exactly the same per credit, so the only reason to move between those two rungs is the extra terabyte and the higher ceiling, not a better rate.
  • The member perk is 10 percent off the same product, carried on each paid record as member_price_id and pointed at while the membership is paid, with the standard price used again when it lapses (stripe_module:211-223). The amounts live in Stripe and no config field holds them, so this page prints no member dollar figure. UNVERIFIED.
  • Repo figures only. The live master config is known to diverge, and config_dev.json prices the same two ids, 200_ai_workspace and 1500_ai_workspace, at $1,100 and $3,500 for entirely different products.
Price per credit, in tenths of a cent
200_ai_workspace, $8
$0.0400 per credit
The cheapest paid rung, and the most expensive credit on the ladder
600_ai_workspace, $25
$0.03125 per credit
$25 divided by 800, not by 600. The id would give a different number
2000_ai_workspace, $75
$0.0300 per credit
Identical to the $45 rung below it, which is the flat step in the curve
6000_ai_workspace, $230
$0.0230 per credit
42.5 percent below the $8 rung, and the floor of the whole ladder
Stacking

On a free membership the workspace tier you paid for is not counted at all.

get_effective_entitlements stacks the workspace record onto the membership allowance only when membership_plan is set and is not the string free (account_module:677-683). On a free membership the record is never read, so neither its ai_credits nor its size is added, and the account is left on the free membership allowance of 30 credits and 15 GB whatever workspace tier it holds. The Drive add-on sitting a few lines below it is treated the opposite way on purpose: it stacks on every membership tier including free (:686-689), with a comment saying that withholding space which has its own invoice line would be selling nothing. So the same function contains both policies, applied to two add-ons, a handful of lines apart. The same paid-membership condition also gates the two generated-image modes in the site builder, gen_low and gen_high, which each require a paid membership and a non-free workspace plan (ai_module:4608-4625). The limit is at the other end of the flow, and it is the one that decides whether this is ever reachable: whether checkout stops a free-membership account from buying a workspace tier in the first place was not traced. UNVERIFIED.

Free membership
Paid membership
Your tier ai_creditsNot read. The branch at :677 never opens, so the number is not added anywhereAdded to the membership allowance as the displayed entitlement
Your tier sizeNot read either, from the same branch, so no storage is addedAdded, and size is the bytes field rather than the storage label
What you are left withThe free membership allowance alone: 30 credits and 15 GBMembership plus whichever workspace tier the account holds
The Drive add-onStacks anyway, on every tier including free (:686-689)Stacks the same way, by the same unconditional path
Generated images in the site buildergen_low and gen_high both refused (ai_module:4608-4625)Both modes available on a non-free workspace plan
From tokens to a balance

The number that becomes your balance is computed outside this repository.

Five stages stand between a model call and the figure the gate reads, and the fourth of them is not in this repository. The first three are: a call returns a usage object, one function prices it against a frozen copy of the catalogue, and one document lands in a database. The fourth is a CouchDB list function that produces the authoritative period total, and the code that calls it says plainly that the raw token cost is not the credit unit the ledger reports. That is why this page prints no credits-per-token figure and no credits-per-dollar figure. Not because the arithmetic is hard, but because the repo defines the shape of the conversion and something else defines its scale.

The finding is the shape of the gap, not a number that fills it. What survives the gap is one ratio, and it is steep: across the five text models the input rate spans 0.05 to 5.00 per million tokens and the output rate spans 0.40 to 30.00 (config.json:520-619), a 100x spread on input and 75x on output. Config names no currency anywhere, and the rates are per million only because every consumer divides by 1e6.
What charges

One function writes every charge, and its source label is the whole audit trail.

Every charge on the platform is one call to account_module.record_ai_usage, which writes a docType ai_usage document and broadcasts the new balance. There is no second billing path, which means the list of source labels is the list of everything that costs you anything. It also means an omission is invisible: a surface that never calls it is free, and nothing in the source states whether that was decided or forgotten.

  • Seventeen live call sites: fifteen in ai_module, one at voice_module:3812 and one in the Xucode proxy at http_module:327, writing sixteen distinct source labels between them. Two of the seventeen can never write a record at all, because they pass no account_profile_info and the function throws on its first line.
  • Email processing charges as submit chat on gpt-1, through two summarisation call sites, one for a text body and one for an HTML body (email_module:2885 and :2898). Contact enrichment reaches the same meter through four ai_module methods called from the contact path: get_business_info, is_business_contact_has_person, get_person_info and get_name_from_email_addr.
  • A realtime voice call accumulates tokens per response.done and flushes one record at hang-up, priced at voice-2, the dearest entry in the catalogue. Transcription is metered differently again: 0 input tokens and the character count of the returned transcript as output tokens against whisper-1. That is what the code does, and it is not a per-minute audio price. Both voice features are switched off in config.
  • Reasoning effort is a per-call argument, not a plan feature and not a user setting. Four values with their own request timeouts, and internal_ai_effort is absent from both config.json and config_dev.json, so every internal job falls back to its hard-coded literal. Effort has no price of its own and moves the bill only through the token count.
Labels a charge is recorded under
submit chat, which is where email summarisation lands too
conversation chat, at whichever model the agent is set to, defaulting to gpt-1
execute codex request, always on a real catalogue code
code run, the background finaliser, model taken from the run
xucode run, written by the proxy that meters the run itself
create image, on img-1 or img-2
transcribe, on whisper-1, billed on transcript characters
profile avatar, virtual avatar, agent avatar and profile image
voice call, one record flushed when the call ends
What does not charge

The biggest chat surface on the platform checks your balance and never debits it.

dashboard_chat calls validate_credits_limit at ai_module:11513 and refuses at zero credits, and there is no record_ai_usage anywhere in its body, from line 11340 to line 12142. It reads the balance and never moves it. chat_studio has neither the gate nor the record: it runs a private npm plugin inside a NodeVM whose whole environment is couch, app_id, uid, userName and agent_info, and the OPENAI_API_KEY belonging to the platform is injected into the plugin setup. AI plugin build and modify go through that same builder. misc_module.submit_chat_gpt_prompt is a second OpenAI client with no billing of any kind. Two limits belong on this block. The builder package is private npm and absent from this repository, so whether it meters itself by some route invisible here is UNVERIFIED; nothing in the sandbox it is handed provides one. The absence of a gate and of a record on the Xuda side is verified by reading the files. And nothing anywhere states whether dashboard_chat is meant to be free or is missing a call.

Refuses at zero credits
Writes a usage record
dashboard_chat, the project assistantYes, validate_credits_limit at ai_module:11513No. Nothing in lines 11340 to 12142
chat_studio, the Studio app builderNo gate anywhere on the pathNo record. The plugin runs in a NodeVM with the platform key
AI plugin build and modifyNo gate. Same builder package, same routeNo record on the Xuda side of the call
misc_module.submit_chat_gpt_promptNo gate. A second OpenAI client entirelyNo billing of any kind
The two shirt-image callsNot gatedThrows first: no account_profile_info (account_module:9477)
The ledger

Seven doors mint a credit, and your workspace tier is not one of them.

The spendable balance is not an entitlement number. It is a ledger: active grant documents minus recorded usage, and the only way a credit enters it is a call to record_ai_credit. There are exactly seven such call sites in cpi, and all seven arrive here as inbound arrows, gathered onto the five rows above the line. The sixth row is the workspace tier itself, which reaches the displayed entitlement and stops there, because both plan-driven grant paths test the category first and refuse anything that is not a membership.

An exhaustive grep for record_ai_credit( across cpi returns exactly seven sites, and they are the seven listed above. The mechanism behind the refusal is worth naming precisely, because it is not a deliberate exclusion so much as a shape mismatch: both plan-driven paths read plan.features?.ai_credits, which only membership records carry as an object. On a workspace record, features is an array of seven marketing sentences, so that read returns undefined. This is read from source and has not been confirmed against a live paid account ledger. UNVERIFIED, and the single most dangerous claim on this page in both directions.
Roll over

Two pools, and only one of them survives the end of a month.

Every credit document carries a kind, and there are two of them (CREDIT_KIND, account_module:9532-9536). Spend comes out of the period pool first, which is the detail that decides everything below: the pool that expires is the pool that drains first, so a bought pack is still there when the plan allowance is retired. Period credits are the allowance for one billing window, and the window is the Stripe invoice period when one is stamped and current, otherwise the calendar month.

One detail cuts the other way, and it belongs here rather than in a footnote: overspend beyond the old allowance is booked against the topup pool at the roll, so credits you bought can absorb an overrun on credits you were given, and that cannot be reclaimed next month. A second limit sits on the pool that is meant to be permanent. ai_credit_packs exists only in config_dev.json, at 100 credits for $10, 500 for $45 and 1200 for $100, and the key is absent from config.json, so purchase_ai_credits would reject every pack id there (stripe_module:3305). Whether the live production config carries the key is unknown.
The refusal

The exact words you get, and which one is yours.

_SCOPE_REASON is a six entry map at account_module:8934-8941. The gate picks the ledger entry when the first breach key is ledger, and otherwise the entry named by the breach scope, falling back to the pool string. Freeze and model access refusals do not come from that map at all: they carry their own strings and their own block_kind. Two surfaces rewrite the refusal for the customer rather than passing the raw string through, the site builder at ai_module:4597-4599 and the project assistant at :12093. The first line below, lowercase and ungrammatical, is the legacy string, and it is the one an ordinary account receives, because only Team and above can save rules at all and an account without rules takes the ledger branch.

scope ledger . the legacy string, and the default account gets this one
ai credits reach to hard limit
scope pool
Your account has reached its AI credit limit.
scope profile
This profile has reached its AI credit limit.
scope user
This member has reached their AI credit limit.
scope model
This model has reached its AI credit limit.
scope source
This action has reached its AI credit limit.
not in that map at all . freeze, block_kind freeze
This profile is paused.
This member is paused.
not in that map either . model access, checked profile rule first
This model is not allowed for this profile.
and what the site builder says instead, ai_module:4599
You’re out of AI credits. Top up to keep generating.
and the project assistant, ai_module:12093
You’re out of AI credits. Top up to keep chatting.
Only a hard block sends the owner email, deduped in process to once per owner, scope and UTC day, with the subject "AI credit limit reached". Freeze and model denial return before the alert call, so neither ever emails.
The gate

One function, a fixed order, and it fails open.

Every AI call goes through account_module.evaluate_credit_gate(req), which takes uid, profile_id, model and source (account_module:8955-9035). ai_module.validate_credits_limit is a thin adapter that turns a block into an error carrying credit_limit_error. Freeze sits before any usage is read, so it bites on the next call, while a cap breach is compared against a cached figure. Model access is three modes: off blocks nothing, deny blocks the listed codes, allow blocks everything not listed, and an empty allow list blocks nothing. The five caps are compared with greater-or-equal, so a hard limit of N refuses at N. Any hard breach refuses with the first one reason and still reports the soft ones; a soft-only breach returns block false and the call runs.

Three limits belong with this picture. The per-scope caps are served from a 45 second in-process cache, so a cap breach can be that stale, while freeze and model access are not. The per-model and per-source aggregation scans up to 200,000 raw documents on a nested field with no Mango index, and the code says to add one before any production rollout. And docs/ishai_ui_requests.md:168 records the feature as dev-verified with prod config staged and not rolled as of 2026-07-22. UNVERIFIED in production.
Credit management

Credit management starts at Team, and two lines of its own shipped copy overstate it.

_is_team_tier compares the account plan rank against the rank of team (free 0, pro 1, team 2 at $149, agency 3, enterprise_t1 through t3 at 4 to 6), and set_credit_rules refuses anything below it with the exact string "Credit management is available on the Team plan." (:9339). get_credit_management is readable on any plan and returns can_edit false, which is why a Free or Pro account sees the pitch page and no editor. One conflict to flag rather than repeat: config declares min_plan "pro" on set_credit_rules, so a Pro account passes the method gate and is then refused by the code. Team is the truthful claim.

  • What actually persists: a warn and a stop limit on the pool, on each profile and on each member; freeze per profile and per member; a model allow or deny list on both; two alert channels. Saving posts the whole rules object through set_credit_rules.
  • Percent is allowed on pool, profile and member only. Model and source caps are forced to absolute credits by the normaliser, values above 100 are clamped to 100, and percent resolves against get_effective_entitlements().ai_credits, the plan allowance. The editor "About N credits" hint is computed from the live ledger grant instead, so the hint is an estimate and not the enforced number.
  • The threshold chips are inert. rules.alerts.thresholds is defaulted to [80, 100], normalised, persisted and rendered as 50, 75, 80, 90 and 100 percent chips, and no code path reads it. Alerts fire off the configured soft limit. credit.vue:135 promising alerts at those percentages is a line to disbelieve.
  • Per-action caps are the weakest control here. Five source strings are ever handed to the gate, and only two of them, voice call and xucode run, match a label usage is actually recorded under, so the other three look up a key with zero spend and never fire. The editor stopped exposing them on 2026-08-11, and they are preserved but not editable.
What a Team owner can actually save
A warn limit and a stop limit on the account pool
The same pair on any one profile
The same pair on any one member
A percent of the plan entitlement instead of a credit number, on those three
Freeze on a profile, blocking every AI call from it
Freeze on a member, checked straight after the profile
A model allow or deny list, per profile and per member
Two alert channels, in the app over the websocket and email to the owner
Changing tier

Up is charged today. Down is charged never, and switches at period end.

Direction is not declared by the caller. _change_direction works it out, and because ai_workspace records carry no rank the comparator falls back to numeric price (stripe_module:335-386). The two sides of that comparison behave nothing alike. An upgrade is money now and a swap on a paid invoice. A downgrade is a Stripe subscription schedule, nothing charged, nothing credited, and a marker the billing screen reads to show a pending change with a cancel button beside it.

Neither path writes ai_workspace_plan. The field is written later, by customer.subscription.updated, which walks every item on the consolidated subscription and resolves each price back to a record by price_id or member_price_id (:1611-1630, :1707-1735). Because direction is price order, it happens to match id order in config.json and does not in config_dev.json. Read from the code path only. No live Stripe schedule was created or observed. UNVERIFIED.
Where the tier is written

One entry point changes a tier, and it never writes the field itself.

The tier is one string on the account document, ai_workspace_plan, holding the PLAN_OBJ key, set to free_ai_workspace when the Stripe customer is first created. Three things move it, and only the last writes it. Worth carrying into the third card: for every other category a placeholder price id is minted into a real Stripe price from the catalog on first purchase, keyed on a lookup key that includes the amount. ai_workspace takes a hard branch at :474-476 and :551-554 and reads the price straight out of the config record, so both TODO ids are handed to Stripe as they are. Reasoned from the code path, no purchase was attempted. UNVERIFIED.

  1. 01

    Checkout calls one method

    checkout.vue:233-280 calls stripe_update_plan for every category, and stripe_get_proration renders the preview through the same classification, so the quote you are shown and the charge you get are computed the same way. Neither one writes ai_workspace_plan. The UI updates local state per category and stops there, and on a scheduled change it deliberately does not flip the plan.

  2. 02

    Stripe swaps the item

    The workspace item on the consolidated subscription is pointed at the new price: the member price while the membership is paid, the standard price when it lapses. Only that one item is ever touched, which is what keeps a workspace change from disturbing the membership, support or drive lines sitting on the same subscription.

  3. 03

    A webhook writes the string

    customer.subscription.updated syncs the field that category maps to through _PLAN_FIELD_BY_CATEGORY and stamps ai_workspace_plan_changed (:358-370, :1733-1735). Miss that webhook and the account keeps its old tier string while Stripe bills the new one. The code carries a self-heal for the membership credit grant and none for this field, so nothing notices and nothing repairs it.

Honest limits

Nine things this page will not tell you, and why.

  • Every figure here comes from the repo copies of config.json and config_dev.json, and the live master config is known to diverge from the repo, so no number on this page is production-confirmed.
  • There is no credits-per-token and no credits-per-dollar figure anywhere above, because the formula is in the repo and the authoritative unit is in a CouchDB design document that is not. The code rescales its own raw figure to that view, and that calibration step is the evidence the two differ.
  • The member price is 10 percent off per the comment at stripe_module:211-223, but the amounts live in Stripe and no config field holds them, so no member dollar figure is printed.
  • The $8 and $45 rungs carry placeholder Stripe price ids, and ai_workspace is the one category that bypasses the minting fallback, so neither belongs on a page with a working buy button yet.
  • config_dev.json prices those same two ids at $1,100 and $3,500 for 50,000 and 200,000 credits. Which product an id names depends on which config the box loaded.
  • A workspace tier ai_credits is never passed to record_ai_credit. It raises the displayed entitlement rather than the spendable balance, established by exhaustive grep and not confirmed against a live paid account.
  • Credit management is recorded as dev-verified with prod config staged and not rolled as of 2026-07-22, and nothing read this session proves a rollout since.
  • The alert threshold chips are stored and never read, and per-action caps are not editable and match a recorded usage label for only two of the five source strings the gate is ever handed.
  • The size on 6000_ai_workspace is 10,737,418,240,000 bytes, which is 10,000 GiB against a 10 TB label, and get_effective_entitlements divides it by 1073741824 and delivers 10,000 GB.
Before you commit

The questions a price list makes you ask

Seven answers, each one ending on the limit it runs into. Two of them end on something we could not confirm at all, and those two say so in the same breath as the claim.

Walkthrough

The walkthrough this page inherits.

This is the recording the previous workspace_plans document already carried on its youtube field, kept here rather than dropped. A page assembled out of config keys, call sites and refusal strings is a poor way to watch the product move, and the walkthrough is the part of the old document that nothing in this rewrite replaces. One rule for watching it. Where the walkthrough and this page disagree about a number, the page is the one built from source in August 2026, read out of config.json and the cpi modules rather than copied forward from the previous wording. That rule earns its place here rather than being politeness, because the published version of this document listed six of the eight workspace tiers and printed the pre rework credit figures against the current storage labels, which left four of its six rows understating the credits they grant. It also carried no price field at all. So a tier name or a credit figure spoken aloud in the walkthrough may be older than the record it describes, and the tables above are what the config file says today.

Every figure on this page was read in August 2026 from the repository copies of config.json and config_dev.json and from the cpi module source beside them. The live master config is known to diverge from the repository, so read each number as accurate to the repository on that date rather than as a production confirmation.

Open the plan screen and read your own record.

Your workspace tier is one string on your account document, ai_workspace_plan, holding the key of a record in the plan catalogue. The screen renders the name field out of that record, which is why the words you see are the current credit figure and the key underneath may still carry the old one. The balance printed beside it is the ledger, and by now you know it is a different number for reasons that have nothing to do with what you paid. Nothing is charged by looking. A downgrade takes no money today, writes a schedule that switches at the natural period end, and stays cancellable until it lands. An upgrade previews the upcoming invoice and tells you the prorated amount before it charges it, and if that charge fails the tier does not move. Two things to check while you are in there. First, whether your membership is free, because the workspace add-on is only stacked when the membership plan is set and is not free, so on a free membership neither its credits nor its storage is counted and the account stays on the membership figures. Second, whether the rung you are reading is one of the two whose Stripe price id in config is still a literal placeholder ending in TODO, because the workspace category is the one category that does not mint a price on demand. Everything above this line was assembled by reading the records rather than the marketing, and the screen you are about to open is reading the same records.