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

# Tracking credit consumption

> Attribute Corti API credit spend to your own customers, features, or workflows using an application-layer wrapper.

Every Corti endpoint that consumes credits reports how many it used in its response. Corti has no visibility into which of your customers, features, or workflows a request belongs to, so if you need to bill customers, attribute cost to a feature, or set per-customer budget alerts, capture that context yourself at the point you make each call. This page covers the pattern we recommend: an **application-layer wrapper** around every credit-consuming call, the same approach commonly used to attribute LLM API spend.

For how credits are priced per product, see [Corti pricing](https://www.corti.ai/pricing).

## How Corti reports usage

The field to read depends on how you call the endpoint:

| Product                                                             | How you call it                       | Where usage appears                                                                                                                                                                   |
| ------------------------------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Text Generation, Medical Coding, fact extraction, batch transcripts | REST request/response                 | `usageInfo.creditsConsumed` in the response body                                                                                                                                      |
| Speech to Text (real-time)                                          | WebSocket (`/transcribe`, `/streams`) | `credits` field on `usage` and `delta_usage` messages                                                                                                                                 |
| Corti Models                                                        | REST, OpenAI-compatible               | `usage.prompt_tokens` / `completion_tokens` (or `input_tokens` / `output_tokens`): tokens, not credits. See [Computing cost for Corti Models](#computing-cost-for-corti-models) below |
| Corti Assistant (embedded)                                          | Client-side event                     | `creditsConsumed` on the [`account.creditsConsumed`](/assistant/events/generated/account/creditsConsumed) event                                                                       |
| Agentic Framework                                                   | REST (`message:send`)                 | `task.metadata.credits` in the response body                                                                                                                                          |

<Note>
  `usageInfo.creditsConsumed` is the same shape on every REST endpoint that returns it. See the response documentation for [Guided Synthesis](/textgen/documents-guided-synthesis#response-shape) and [Medical Coding](/coding/how-it-works), or the schema on the [Extract Facts](/api-reference/facts/extract-facts) and [Create Transcript](/api-reference/transcripts/create-transcript) reference pages.
</Note>

The Agentic Framework reports credits differently: `task.metadata.credits` on the `message:send` response, not a top-level `usageInfo` object. This shape isn't in the OpenAPI spec yet, but the field is present on every message response and safe to read.

For real-time streaming, `delta_usage` is an approximate running total sent after each `flush`; the final `usage` message sent after `end` is authoritative. See [Speech to Text usage messages](/api-reference/transcribe#usage) (the same shape applies to `/streams`).

## Computing cost for Corti Models

Corti Models follows the OpenAI-compatible spec, so it returns token counts, not a credits figure. Use [`@pydantic/genai-prices`](https://github.com/pydantic/genai-prices) ([JavaScript/TypeScript on npm](https://www.npmjs.com/package/@pydantic/genai-prices), [Python on PyPI](https://pypi.org/project/genai-prices/)) to compute the USD cost from the response's `usage` object and the [documented per-model rates](/models/models#pricing).

Corti's models aren't in the library's bundled catalog, so register them as a custom provider with the current rates. The library handles the per-million-token math and the cached-input discount (90% off the standard input rate) automatically — you just map the OpenAI-format usage fields to the library's `Usage` shape:

| OpenAI Chat Completions                        | genai-prices `Usage` |
| ---------------------------------------------- | -------------------- |
| `usage.prompt_tokens` (includes cached tokens) | `input_tokens`       |
| `usage.completion_tokens`                      | `output_tokens`      |
| `usage.prompt_tokens_details.cached_tokens`    | `cache_read_tokens`  |

<Note>
  For the **Responses API**, map `usage.input_tokens` / `usage.output_tokens` instead. For **embeddings**, pass `input_tokens` only — the library skips output cost when no output rate is set.
</Note>

<CodeGroup>
  ```ts title="JavaScript" expandable theme={null}
  import OpenAI from "openai";
  import { calcPrice, type Provider } from "@pydantic/genai-prices";

  // Replace these with your values
  const API_KEY = "<your-api-key>";
  const MODEL = "corti-s1-mini";

  // Corti Models pricing in USD per million tokens. Corti's models aren't in the
  // genai-prices catalog, so register them as a custom provider.
  // See /models/models#pricing for current rates.
  const cortiProvider: Provider = {
    id: "corti",
    name: "Corti Models",
    api_pattern: "corti\\.app",
    models: [
      { id: "corti-s1", match: { equals: "corti-s1" }, prices: { input_mtok: 2.0, cache_read_mtok: 0.2, output_mtok: 8.0 } },
      { id: "corti-s1-instant", match: { equals: "corti-s1-instant" }, prices: { input_mtok: 2.0, cache_read_mtok: 0.2, output_mtok: 8.0 } },
      { id: "corti-s1-mini", match: { equals: "corti-s1-mini" }, prices: { input_mtok: 1.0, cache_read_mtok: 0.1, output_mtok: 4.0 } },
      { id: "corti-s1-mini-instant", match: { equals: "corti-s1-mini-instant" }, prices: { input_mtok: 1.0, cache_read_mtok: 0.1, output_mtok: 4.0 } },
      { id: "corti-s1-embedding", match: { equals: "corti-s1-embedding" }, prices: { input_mtok: 0.03 } },
    ],
  };

  const client = new OpenAI({
    baseURL: "https://ai.eu.corti.app/v1",
    apiKey: API_KEY,
  });

  const response = await client.chat.completions.create({
    model: MODEL,
    messages: [{ role: "user", content: "Summarize this encounter." }],
  });

  // Map the OpenAI-format usage to genai-prices Usage. prompt_tokens includes
  // cached tokens; cache_read_tokens is the cached subset priced at the discount.
  const usage = {
    input_tokens: response.usage?.prompt_tokens,
    output_tokens: response.usage?.completion_tokens,
    cache_read_tokens: response.usage?.prompt_tokens_details?.cached_tokens,
  };

  const result = calcPrice(usage, response.model, { provider: cortiProvider });

  if (result) {
    console.log(`$${result.total_price.toFixed(6)} USD`);
  }
  ```

  ```python title="Python" expandable theme={null}
  from decimal import Decimal

  from openai import OpenAI
  from genai_prices import Usage
  from genai_prices.types import ModelPrice

  # Replace these with your values
  API_KEY = "<your-api-key>"
  MODEL = "corti-s1-mini"

  # Corti Models pricing in USD per million tokens.
  # See /models/models#pricing for current rates.
  CORTI_MODELS_PRICING = {
      "corti-s1": ModelPrice(input_mtok=Decimal("2.0"), cache_read_mtok=Decimal("0.2"), output_mtok=Decimal("8.0")),
      "corti-s1-instant": ModelPrice(input_mtok=Decimal("2.0"), cache_read_mtok=Decimal("0.2"), output_mtok=Decimal("8.0")),
      "corti-s1-mini": ModelPrice(input_mtok=Decimal("1.0"), cache_read_mtok=Decimal("0.1"), output_mtok=Decimal("4.0")),
      "corti-s1-mini-instant": ModelPrice(input_mtok=Decimal("1.0"), cache_read_mtok=Decimal("0.1"), output_mtok=Decimal("4.0")),
      "corti-s1-embedding": ModelPrice(input_mtok=Decimal("0.03")),
  }

  client = OpenAI(base_url="https://ai.eu.corti.app/v1", api_key=API_KEY)

  response = client.chat.completions.create(
      model=MODEL,
      messages=[{"role": "user", "content": "Summarize this encounter."}],
  )

  # Map the OpenAI-format usage to genai-prices Usage. prompt_tokens includes
  # cached tokens; cache_read_tokens is the cached subset priced at the discount.
  details = response.usage.prompt_tokens_details
  usage = Usage(
      input_tokens=response.usage.prompt_tokens,
      output_tokens=response.usage.completion_tokens,
      cache_read_tokens=details.cached_tokens if details else None,
  )

  price = CORTI_MODELS_PRICING[response.model].calc_price(usage)
  print(f"${price['total_price']} USD")
  ```
</CodeGroup>

<Note>
  A future release of `GET /models` will return each model's per-token pricing directly, so you can look up rates dynamically instead of hardcoding them in the custom provider. For now, refer to the [documented rates](/models/models#pricing).
</Note>

## The application-layer wrapper pattern

Treat every credit-consuming request as a billing event you tag, measure, and log yourself. Corti doesn't expose which customer or feature a past request belonged to after the fact, only an aggregate total is queryable retroactively, and only under the conditions described in [Limitations](#limitations) below. Capture attribution at request time instead of trying to reconstruct it later.

Rather than adding tracking code at every call site, wrap the calls once:

1. **Tag** the request with your own metadata: which customer or end-user it's for, and which feature or workflow triggered it.
2. **Call** the Corti endpoint as usual.
3. **Read** the credits or tokens consumed from the response (see the table above for which field).
4. **Log** one structured event per request to your own datastore: request ID, timestamp, feature, customer ID, credits consumed, latency, and status.

<CodeGroup>
  {/* Wrap a REST call to tag it with feature/customer metadata and log credits consumed */}

  ```ts title="JavaScript" expandable theme={null}
  // Replace these with your values
  const CUSTOMER_ID = "<your-customer-id>";
  const FEATURE = "<id-of-your-choosing>";
  const INTERACTION_ID = "<your-interaction-id>";
  const TEMPLATE_ID = "<your-template-id>";

  async function trackCreditUsage(feature, customerId, requestFn) {
    const requestId = crypto.randomUUID();
    const startedAt = Date.now();

    try {
      const result = await requestFn();

      // Write to your own store: a database row, a warehouse insert, or a structured log line.
      await logUsageEvent({
        requestId,
        feature,
        customerId,
        creditsConsumed: result.usageInfo.creditsConsumed,
        latencyMs: Date.now() - startedAt,
        status: "ok",
      });

      return result;
    } catch (error) {
      await logUsageEvent({
        requestId,
        feature,
        customerId,
        creditsConsumed: 0,
        latencyMs: Date.now() - startedAt,
        status: "error",
        errorCode: error.status ?? "unknown",
      });
      throw error;
    }
  }

  const result = await trackCreditUsage(FEATURE, CUSTOMER_ID, () =>
    client.documents.generate({
      outputLanguage: "en-US",
      interactionId: INTERACTION_ID,
      templateRef: { templateId: TEMPLATE_ID },
    })
  );
  ```

  {/* Wrap a REST call to tag it with feature/customer metadata and log credits consumed */}

  ```csharp title="C# .NET" expandable theme={null}
  using Corti;

  // Replace these with your values
  const string CUSTOMER_ID = "<your-customer-id>";
  const string FEATURE = "<id-of-your-choosing>";
  const string INTERACTION_ID = "<your-interaction-id>";
  const string TEMPLATE_ID = "<your-template-id>";

  async Task<GuidedDocumentsCreateResponse> TrackCreditUsageAsync(
      string feature,
      string customerId,
      Func<Task<GuidedDocumentsCreateResponse>> requestFn)
  {
      var requestId = Guid.NewGuid();
      var startedAt = DateTimeOffset.UtcNow;

      try
      {
          var result = await requestFn();

          // Write to your own store: a database row, a warehouse insert, or a structured log line.
          await LogUsageEventAsync(new
          {
              RequestId = requestId,
              Feature = feature,
              CustomerId = customerId,
              CreditsConsumed = result.UsageInfo.CreditsConsumed,
              LatencyMs = (DateTimeOffset.UtcNow - startedAt).TotalMilliseconds,
              Status = "ok",
          });

          return result;
      }
      catch (Exception ex)
      {
          await LogUsageEventAsync(new
          {
              RequestId = requestId,
              Feature = feature,
              CustomerId = customerId,
              CreditsConsumed = 0,
              LatencyMs = (DateTimeOffset.UtcNow - startedAt).TotalMilliseconds,
              Status = "error",
              ErrorMessage = ex.Message,
          });
          throw;
      }
  }

  var result = await TrackCreditUsageAsync(FEATURE, CUSTOMER_ID, () =>
      client.Documents.GenerateAsync(
          new GuidedDocumentsGenerateByTemplateRef
          {
              OutputLanguage = "en-US",
              InteractionId = INTERACTION_ID,
              TemplateRef = new GuidedTemplateRef { TemplateId = TEMPLATE_ID },
          }));
  ```
</CodeGroup>

Apply the same pattern to WebSocket streaming by logging from the `usage` message instead of a REST response:

<CodeGroup>
  {/* Log credits consumed from a real-time streaming session's usage message */}

  ```ts title="JavaScript" theme={null}
  // Replace these with your values
  const CUSTOMER_ID = "<your-customer-id>";
  const FEATURE = "<id-of-your-choosing>";

  socket.on("message", (msg) => {
    if (msg.type === "usage") {
      logUsageEvent({
        feature: FEATURE,
        customerId: CUSTOMER_ID,
        creditsConsumed: msg.credits,
        status: "ok",
      });
    }
  });
  ```

  {/* Log credits consumed from a real-time streaming session's usage message */}

  ```csharp title="C# .NET" theme={null}
  // Replace these with your values
  const string CUSTOMER_ID = "<your-customer-id>";
  const string FEATURE = "<id-of-your-choosing>";

  transcribe.TranscribeUsageMessage.Subscribe(message =>
  {
      LogUsageEvent(new
      {
          Feature = FEATURE,
          CustomerId = CUSTOMER_ID,
          CreditsConsumed = message.Credits,
          Status = "ok",
      });
  });
  ```
</CodeGroup>

### What to record per event

| Field                                          | Why                                                                                                                                                                                                              |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A request or correlation ID                    | Deduplicates retries so you don't double-count credits. Use your own generated ID, and also store the `interactionId` when the call is tied to an [interaction](/api-reference/interactions/create-interaction). |
| Timestamp                                      | Time-series queries and monthly rollups.                                                                                                                                                                         |
| Feature or workflow                            | The product surface that triggered the call (e.g. `encounter-summary`, `dictation-note`). Lets you attribute spend by feature, not just by customer.                                                             |
| Customer or end-user ID                        | Your own identifier, not Corti's. Required for per-customer billing or budget alerts.                                                                                                                            |
| Credits consumed (or tokens, for Corti Models) | The number you'll bill or alert on.                                                                                                                                                                              |
| Status / error code                            | Failed requests can still consume partial credits; don't drop them from your log.                                                                                                                                |

<Tip>
  Reject events with missing metadata instead of defaulting to "unknown". An unattributed cost event is harder to fix after the fact than a wrapper call site you forgot to update.
</Tip>

## Aggregate and alert

Once events land in your own store, attribution is a query, not an integration problem:

* Sum credits by customer ID and time window for chargeback or invoicing.
* Sum by feature to see which parts of your product are most expensive to run.
* Alert when a customer's rolling spend crosses a threshold you define.

## Corti Assistant: the event is the wrapper

If you're integrating Corti Assistant rather than calling the Corti API directly, you don't need to build a wrapper. The embedded widget already emits an [`account.creditsConsumed`](/assistant/events/generated/account/creditsConsumed) event after every stream, transcription, or document generation. Listen for it and log the same fields you would from a wrapper: `creditsConsumed`, `reason`, and `interactionId` are all in the public (non-confidential) payload, so you don't need to handle patient data to attribute cost.

```ts theme={null}
corti.addEventListener("account.creditsConsumed", (event) => {
  const { creditsConsumed, reason, interactionId } = event.detail;
  logUsageEvent({ creditsConsumed, reason, interactionId, customerId: currentCustomerId });
});
```

See [Corti Assistant events](/assistant/events) for the full event system and transport options (Web Component, postMessage, or window API).

## Limitations

* **Corti Models** doesn't return a credits figure directly; see [Computing cost for Corti Models](#computing-cost-for-corti-models) above.
* The [Admin API](/about/admin-api) exposes an aggregate consumption endpoint (total credits for one of your Embedded Assistant end-users over a time window), but it reports a total, not a per-request or per-feature breakdown. Treat it as a cross-check against your own totals, not a substitute for wrapper-level attribution.
