> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modulex.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Credits & metering

> Exactly how ModuleX meters managed usage in credits: the 100-credit-per-dollar unit, the per-operation cost table (run, retrieval, ingest, tool, LLM tokens), the reserve to charge to settle lifecycle, and why BYOK usage is never credited.

export const MediaEmbed = ({id, type = 'screenshot', caption = '', ext, ratio = '16 / 9'}) => {
  const isVideo = type === 'video' || type === 'app_video';
  const resolvedExt = ext || (isVideo ? 'mp4' : type === 'screenshot' ? 'webp' : 'svg');
  const src = 'https://media.modulex.dev/' + id + '.' + resolvedExt;
  const [status, setStatus] = useState('loading');
  const [isDev, setIsDev] = useState(false);
  const [inView, setInView] = useState(false);
  const boxRef = useRef(null);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const h = window.location.hostname;
    setIsDev(h === 'localhost' || h === '127.0.0.1' || h.endsWith('.mintlify.app'));
  }, []);
  useEffect(() => {
    if (inView) return;
    if (typeof IntersectionObserver === 'undefined') {
      setInView(true);
      return;
    }
    const el = boxRef.current;
    if (!el) return;
    const io = new IntersectionObserver(entries => {
      if (entries.some(e => e.isIntersecting)) {
        setInView(true);
        io.disconnect();
      }
    }, {
      rootMargin: '300px'
    });
    io.observe(el);
    return () => io.disconnect();
  }, [inView]);
  if (status === 'missing') {
    if (!isDev) return null;
    return <div style={{
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      gap: '0.4rem',
      padding: '1rem 1.25rem',
      margin: '1.25rem 0',
      width: '100%',
      aspectRatio: ratio,
      boxSizing: 'border-box',
      border: '1px dashed rgba(128,128,128,0.45)',
      borderRadius: '0.75rem',
      background: 'rgba(128,128,128,0.06)',
      color: 'currentColor',
      fontSize: '0.85rem',
      lineHeight: 1.45
    }}>
        <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '0.5rem',
      opacity: 0.75
    }}>
          <span aria-hidden="true">🎬</span>
          <code style={{
      fontSize: '0.75rem'
    }}>{id}</code>
          <span style={{
      fontSize: '0.65rem',
      textTransform: 'uppercase',
      letterSpacing: '0.04em',
      padding: '0.1rem 0.4rem',
      borderRadius: '0.4rem',
      background: 'rgba(128,128,128,0.18)'
    }}>
            {type}
          </span>
        </div>
        <div style={{
      opacity: 0.9
    }}>{caption || 'Media not uploaded yet.'}</div>
        <div style={{
      fontSize: '0.7rem',
      opacity: 0.5
    }}>
          Upload to R2 as <code>{id}.{resolvedExt}</code> — preview only, hidden in production.
        </div>
      </div>;
  }
  const mediaStyle = {
    display: status === 'loaded' ? 'block' : 'none',
    width: '100%',
    height: 'auto',
    borderRadius: '0.75rem'
  };
  const media = isVideo ? <video src={inView ? src : undefined} autoPlay loop muted playsInline preload="metadata" onLoadedData={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} /> : <img src={inView ? src : undefined} alt={caption} onLoad={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} />;
  return <figure style={{
    margin: '1.25rem 0'
  }}>
      <div ref={boxRef} style={status === 'loaded' ? {
    width: '100%'
  } : {
    width: '100%',
    aspectRatio: ratio,
    borderRadius: '0.75rem',
    background: 'rgba(128,128,128,0.06)'
  }}>
        {media}
      </div>
      {status === 'loaded' && caption ? <figcaption style={{
    marginTop: '0.5rem',
    textAlign: 'center',
    fontSize: '0.85rem',
    opacity: 0.7
  }}>
          {caption}
        </figcaption> : null}
    </figure>;
};

A **credit** is the unit ModuleX uses to meter **managed** usage — anything that runs
through ModuleX-provisioned model, tool, or knowledge infrastructure. This page is the
exact reference for what a credit is worth, what every operation costs, how a charge moves
through the **reserve → charge → settle** lifecycle, and why **bring-your-own-key (BYOK)**
usage is never credited.

Credits only cover the metering side of billing. For the admission gate that turns an
empty allowance into a `402` / `403` / `429`, see [Usage gating & limits](/billing/usage-gating);
for the prepaid balance that funds spend beyond your plan, see [Wallet & top-ups](/billing/wallet);
for the per-plan allowances, see [Plans & pricing](/billing/plans).

## The credit unit

The whole system is anchored to one constant: **100 credits = $1.00**, so **1 credit =
$0.01**. This is the same value the app surfaces in its credit tooltips and the same value
the wallet uses to convert dollars to credits.

<ResponseField name="SCALE" type="integer" default="100">
  Credits per US dollar. `100` credits = `$1.00`.
</ResponseField>

<ResponseField name="CREDIT_TO_USD" type="number" default="0.01">
  Dollars per credit, derived as `1 / SCALE`. Used to convert a wallet top-up amount into
  credits and to value credit usage in dollars.
</ResponseField>

Each organization receives a monthly **credit allowance** from its [plan](/billing/plans).
Because the unit is fixed at $0.01, each plan's monthly allowance has a clear dollar value:
Pro's 5,000 credits is $50.00 of managed usage, and Max's 20,000 credits is \$200.00. The
Free plan grants 300 credits as a one-time trial allowance rather than a monthly grant.

<Note>
  **Two numbers carry the FREE allowance, and they are not the same layer.** The
  organization-level FREE allowance resolves to **300** credits, while the per-user free
  credit pool defaults its `cap` to **500**. These are
  different layers (an org grant versus a per-user pool cap), not a single contradicted
  value. If you are reconciling a FREE account's balance, check which layer you are reading.
</Note>

## What consumes credits

Only **managed** usage is metered. An operation is managed when it runs through ModuleX's
own provider pool — that is, when its integration name is `modulexai` (managed
[LLM and tool](/integrations/llm-providers/modulexai) calls) / `modulexdb`
([managed knowledge](/integrations/knowledge-providers/modulexdb)), or when the API key
source is the platform environment. Everything else — your own provider keys — is BYOK and
is **not** credited (see [BYOK is never credited](#byok-is-never-credited)).

There are two cost families: **flat per-operation costs**, and **token-metered LLM costs**.

### Flat per-operation costs

These operations cost a fixed number of credits regardless of size.

| Operation             | `usage_type`  |   Cost (credits)   | When it is charged                                                                                                                                  |
| --------------------- | ------------- | :----------------: | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Workflow / agent run  | `run`         |        **1**       | Once per logical run or agent turn. A [resumed](/realtime/hitl) turn is not charged again.                                                          |
| Knowledge retrieval   | `retrieval`   |        **1**       | Per managed-knowledge search or retrieve call.                                                                                                      |
| Document ingest       | `file_ingest` |        **1**       | Per ingested document. Idempotent on the document ID, so a retried ingest of the same document is not double-charged.                               |
| Integration tool call | (tool)        | **1 × multiplier** | Per managed [tool](/workflow-builder/nodes/tool) action. The base of `1` credit is anchored to \$0.01; the per-pool `multiplier` defaults to `1.0`. |

<ParamField path="RUN_CREDIT" type="integer" default="1">
  Credits charged once per logical run or agent turn, written as one `usage_type="run"`
  row.
</ParamField>

<ParamField path="RETRIEVAL_BASE" type="integer" default="1">
  Credits per managed-knowledge search or retrieval call (`usage_type="retrieval"`).
</ParamField>

<ParamField path="FILE_INGEST_BASE" type="integer" default="1">
  Credits per ingested document (`usage_type="file_ingest"`), idempotent on the document
  ID.
</ParamField>

<ParamField path="TOOL_BASE" type="integer" default="1">
  Base credits for a managed integration tool call, equal to `SCALE / 100` (anchored to
  \$0.01). The final tool cost is `TOOL_BASE × multiplier`, where `multiplier` comes from
  the managed key pool and defaults to `1.0`.
</ParamField>

<Note>
  A managed run that also retrieves knowledge and calls a tool accrues **separate** charges:
  one `run` credit, one `retrieval` credit per search, and the tool cost — plus any LLM token
  cost (below). Costs are additive across operation types within a single run.
</Note>

### Token-metered LLM cost

Managed LLM and embedding calls are metered on **tokens**, not a flat fee. The cost is
computed from the model's per-million-token input and output rates, plus a fixed system
margin, then converted to credits:

```text Credit cost formula theme={null}
cost = ( prompt_tokens   × input_rate  / 1_000_000
       + completion_tokens × output_rate / 1_000_000 )
       × MARGIN
       × SCALE
```

<ParamField path="MARGIN" type="number" default="1.05">
  The system margin applied to every token-metered cost — a flat +5%.
</ParamField>

The result is quantized to a fixed-precision decimal before it is
recorded. A few rules to be aware of:

<Expandable title="Token-metering rules and edge cases">
  * **Embeddings are input-only.** Embedding calls force `completion_tokens = 0`, so you pay
    only for the input tokens.
  * **Rates come from the model pricing table.** Per-token rates are resolved by the model's
    identifier; if a specific model is not priced, ModuleX falls back to a `__default__`
    rate, and if that is also absent the rate is treated as **0** — never a silent `1.0`. An
    unpriced model therefore costs nothing rather than overcharging.
  * **The margin is fixed.** `MARGIN = 1.05` is applied uniformly; there is no per-model
    margin override in the cost path.
  * **BYOK LLM calls are not metered here.** If the call uses your own provider key, no
    credit cost is computed — the provider bills you directly.
</Expandable>

<Note>
  **Why an unpriced model can read as free.** Token metering deliberately fails to `0` rather
  than to a default multiplier, so a newly added model with no pricing row will not be
  overcharged. This is the intended conservative behavior; do not rely on it as a pricing
  strategy.
</Note>

## The reserve → charge → settle lifecycle

Every managed operation passes through a three-stage credit lifecycle. The gate runs
**before** any database write so a denied operation never creates partial state, and
the durable managed datastore — not the fast reservation store — is the source of truth
for what you have actually spent.

<Steps>
  <Step title="Reserve (admission)">
    Before the operation does any work, the admission gate resolves your plan's entitlement
    bundle and **hard-reserves** the credits the operation will need. The reservation is an
    atomic operation in the fast reservation store, guarded by a datastore-sum fallback if
    that store is unavailable. If
    the reservation fails — no allowance, no wallet overage, a suspended subscription, or a
    rate limit — the gate raises a denial and **nothing is written**. This is where a
    `402` / `403` / `429` [`DenialEnvelope`](/api-reference/errors) comes from.
  </Step>

  <Step title="Charge (settle on success)">
    When the operation completes, ModuleX writes **one** usage row recording the
    `usage_type` and `credit_cost`, then settles the reservation from *reserved* to
    *used*. The charge is **idempotent** on an idempotency key, so the same run or turn is
    never charged twice — including across a resume.
  </Step>

  <Step title="Release (on early failure)">
    If the operation errors or is cancelled before it is charged, the gate **releases** the
    never-costed reservation so the held budget self-heals immediately rather than waiting
    for the reservation's time-to-live to expire.
  </Step>
</Steps>

<Note>
  **A resumed turn is not re-charged.** When a paused run or agent turn resumes, the charge
  step is a no-op for the already-charged turn, and admin-scope usage is never charged at all.
  The idempotency key — derived from the chat and run identifiers for an agent turn, or the
  document ID for an ingest — guarantees one charge per logical unit of work.
</Note>

<MediaEmbed id="MX-MEDIA-1260" type="image" caption={"A horizontal lifecycle diagram of a single managed operation moving through reserve, charge, and release."} />

### What a usage row records

Each charge produces one organization credit-usage row. The fields you will see when you
inspect usage are:

<ResponseField name="usage_type" type="string">
  The kind of charge: `run`, `retrieval`, `file_ingest`, or an LLM/embedding token charge.
  Tool calls are recorded through the credential-usage path.
</ResponseField>

<ResponseField name="credit_cost" type="number">
  The credits charged for this operation. `1` for `run`, `retrieval`, and `file_ingest`;
  the computed value for token-metered calls; `TOOL_BASE × multiplier` for tools.
</ResponseField>

<ResponseField name="idempotency_key" type="string">
  The dedup key enforced by a partial-unique constraint. A duplicate key is swallowed
  rather than charged again, which is how resumes and retried ingests avoid double charges.
</ResponseField>

<ResponseField name="month_offset" type="integer">
  The monthly billing-bucket ordinal that partitions paid usage per period. Computed as
  `year × 12 + (month − 1)`, with the boundary on your subscription's anchor day. FREE
  usage uses `0`.
</ResponseField>

## How charges draw down your balance

Charges are drawn from your allowances in a fixed order: plan allowance first, then the
wallet.

<Steps>
  <Step title="Plan allowance">
    Each managed charge first draws from your plan's monthly credit allowance for the
    current `month_offset` bucket. While the allowance covers the charge, the reservation
    resolves against the plan and nothing touches the wallet.
  </Step>

  <Step title="Wallet overage (paid plans only)">
    When the plan allowance for the bucket is exhausted, a **paid** organization with
    overage enabled spends down its prepaid [wallet](/billing/wallet) at the same \$0.01
    per credit. Overage is off by default and can only be enabled on a paid plan. If
    overage is disabled or the wallet balance is insufficient, the gate denies the
    operation with a `wallet` -layer `402`.
  </Step>
</Steps>

<Warning>
  **Free organizations have no overage path.** The wallet overage cascade is paid-plan only.
  On the Free plan, once the one-time allowance and the per-user free pool are spent — or once
  the account ages past its free-tier window — managed operations are denied with a `credit`
  -layer `402`. There is no wallet to fall back to.
</Warning>

## Denials raised by the gate

When the reserve step fails, the gate raises a flat
[`DenialEnvelope`](/api-reference/errors) — `{code, layer, key, current, limit, reason}` —
with an HTTP status derived from the layer. These are the credit-relevant denials you will
see while metering managed usage:

| `code`                    | `layer`  | HTTP | Meaning                                                                                           |
| ------------------------- | -------- | :--: | ------------------------------------------------------------------------------------------------- |
| `credit_plan_exhausted`   | `credit` |  402 | Plan allowance is used up and no wallet overage is available (or the subscription is suspended).  |
| `wallet_overage_disabled` | `wallet` |  402 | Allowance exhausted and overage is turned off for the organization.                               |
| `wallet_insufficient`     | `wallet` |  402 | Overage is on, but the prepaid wallet balance is too low.                                         |
| `quota_exceeded`          | `quota`  |  403 | A countable entitlement (for example, the number of knowledge bases) is at its limit.             |
| `rate_limit_exceeded`     | `rate`   |  429 | A per-period run/usage rate limit was reached. Carries `Retry-After` and `X-RateLimit-*` headers. |

<Note>
  The flat `DenialEnvelope` is **only** raised on the gated surfaces — workflow runs,
  [Composer](/concepts/ai-composer), [Assistant](/concepts/assistant), and managed-knowledge
  operations. The wallet management routes return a *different* `402` shape
  (`{"detail": {"reason": "paid_subscription_required"}}`), and plain CRUD routes never emit a
  `DenialEnvelope` at all. See [Errors & status codes](/api-reference/errors) for all four
  envelope shapes and [Usage gating & limits](/billing/usage-gating) for the gate itself.
</Note>

Example `402` body for an exhausted plan allowance:

```json 402 — credit_plan_exhausted theme={null}
{
  "code": "credit_plan_exhausted",
  "layer": "credit",
  "key": "9a2f7c41-0e3b-4d8a-b1c2-7f6e5d4c3b2a",
  "current": null,
  "limit": 5000.0,
  "reason": "credit_plan_exhausted"
}
```

## BYOK is never credited

If you connect your own model, tool, or vector-store provider with your own API key, that
usage is **bring-your-own-key (BYOK)**. ModuleX classifies it as analytics-only: it is
logged for usage visibility but **never** charged in credits, and the upstream provider
bills you directly.

<CardGroup cols={2}>
  <Card title="Managed usage (credited)" icon="credit-card">
    Runs through ModuleX's provider pool (`modulexai` / `modulexdb`, or a platform key).
    Metered in credits using the costs above. Subject to the
    [usage gate](/billing/usage-gating).
  </Card>

  <Card title="BYOK usage (uncredited)" icon="key">
    Runs through your own provider key. Recorded for analytics only, **not** charged in
    credits. Billed by your provider, with no ModuleX markup.
  </Card>
</CardGroup>

<Warning>
  **BYOK turns off credit metering, not the rest of the gate.** Because no credit cost is
  computed for a BYOK call, it does not draw down your allowance or wallet — but countable
  entitlements (such as knowledge-base quotas) and rate limits still apply, and those denials
  are still raised as a `quota` (403) or `rate` (429) `DenialEnvelope`.
</Warning>

## Inspecting credit usage

There is no dedicated REST endpoint or SDK method for *reserving* or *charging* credits —
that happens server-side inside the gate. You observe credit usage through the dashboard
analytics surface, which both SDKs expose. The example below reads the analytics overview,
which includes the current month's credit usage. Authentication uses
`Authorization: Bearer mx_live_…` plus the
[`X-Organization-ID`](/api-reference/authentication) header.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.modulex.dev/dashboard/analytics/overview \
    -H "Authorization: Bearer mx_live_8Kd2pQ7nR4xWvL0eYbT1" \
    -H "X-Organization-ID: org_9a2f7c41"
  ```

  ```python Python theme={null}
  from modulex import Modulex

  async with Modulex(
      api_key="mx_live_8Kd2pQ7nR4xWvL0eYbT1",
      organization_id="org_9a2f7c41",
  ) as client:
      overview = await client.dashboard.analytics_overview()
      print(overview)
  ```

  ```javascript JavaScript theme={null}
  import { Modulex } from 'modulex-js';

  const client = new Modulex({
    apiKey: 'mx_live_8Kd2pQ7nR4xWvL0eYbT1',
    organizationId: 'org_9a2f7c41',
  });

  const overview = await client.dashboard.analyticsOverview();
  console.log(overview);
  ```
</CodeGroup>

<Warning>
  **Dashboard analytics return HTTP 200 even on failure.** All dashboard routes return
  `200` with a `success: false` envelope when something goes wrong, instead of an error
  status, so the SDK error path does not fire. Branch on the `success` field in the response
  rather than relying on a thrown error. See the
  [SDK ⇄ API parity matrix](/sdks/parity) for this behavior.
</Warning>

<Note>
  **Wallet top-ups and subscription changes are managed elsewhere.** Funding the wallet,
  toggling overage, and changing plans are part of the subscription surface, and the
  `subscriptions` resource exists **only in the Python SDK** — the JavaScript SDK has no
  subscriptions methods, and the wallet/top-up routes have no SDK method in either SDK. See
  [Wallet & top-ups](/billing/wallet) and [Subscriptions & Stripe](/billing/subscription-lifecycle).
</Note>

## Related pages

<CardGroup cols={2}>
  <Card title="Usage gating & limits" icon="shield-halved" href="/billing/usage-gating">
    The admission gate that reserves credits and raises the 402 / 403 / 429 denial.
  </Card>

  <Card title="Wallet & top-ups" icon="wallet" href="/billing/wallet">
    The prepaid balance that funds managed usage beyond your plan allowance.
  </Card>

  <Card title="Plans & pricing" icon="layer-group" href="/billing/plans">
    The per-plan monthly credit allowances, rate limits, and quotas.
  </Card>

  <Card title="Errors & status codes" icon="triangle-exclamation" href="/api-reference/errors">
    The flat DenialEnvelope and the other error-envelope shapes.
  </Card>

  <Card title="Managed knowledge" icon="database" href="/platform/knowledge/managed">
    Where retrieval and ingest credits are spent.
  </Card>

  <Card title="Glossary" icon="book" href="/reference/glossary">
    Canonical definitions for credit, wallet, overage, and the gate terms.
  </Card>
</CardGroup>
