> ## Documentation Index
> Fetch the complete documentation index at: https://growthx-changeset-release-main.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Cost Events

> Monitor LLM and HTTP request costs in real time — cost:llm:request and cost:http:request hook events for logging, alerting, and aggregation

The [cost estimation CLI](/costs) shows you costs after a workflow finishes. **Cost events** expose similar data while a workflow runs:

* **`cost:llm:request`** — emitted when an LLM call finishes (`generateText` / `streamText`), with an LLM usage attribute.
* **`cost:http:request`** — emitted when you attach a dollar cost to an HTTP response with [`addRequestCost`](/packages/http#attaching-request-cost) from `@outputai/http`, with an HTTP request cost attribute.

Use either or both to log spend to your observability stack, trigger alerts, or aggregate cost per workflow over time.

## Setup

Cost events use the same [hooks system](/operations/error-hooks) as error hooks:

1. Create a hook file and import `on` from `@outputai/core/hooks`.
2. Register a handler for `cost:llm:request`, `cost:http:request`, or both.
3. Add the file path to `outputai.hookFiles` in `package.json`.

See [Error Hooks — Setup](/operations/error-hooks#setup) for the hook file registration pattern.

```typescript src/llm_cost_hooks.ts theme={null}
import { on } from '@outputai/core/hooks';
import type { LLMUsageEvent } from '@outputai/llm';

on<LLMUsageEvent>( 'cost:llm:request', async ( { eventId, eventDate, workflowDetails, activityInfo, payload } ) => {
  if (!workflowDetails || !activityInfo || !payload) {
    return;
  }

  console.log( 'LLM call', {
    eventId,
    eventDate,
    workflowId: workflowDetails.workflowId,
    activityId: activityInfo.activityId,
    modelId: payload.modelId,
    tokens: payload.tokensUsed,
    cost: payload.total,
    usage: payload.usage
  } );
} );
```

```typescript src/http_cost_hooks.ts theme={null}
import { on } from '@outputai/core/hooks';
import type { HttpRequestCostEvent } from '@outputai/http';

on<HttpRequestCostEvent>( 'cost:http:request', async ( { eventId, eventDate, workflowDetails, activityInfo, payload } ) => {
  if (!workflowDetails || !activityInfo || !payload) {
    return;
  }

  console.log( 'HTTP request', {
    eventId,
    eventDate,
    workflowId: workflowDetails.workflowId,
    activityId: activityInfo.activityId,
    requestId: payload.requestId,
    url: payload.url,
    total: payload.total
  } );
} );
```

Handler errors are caught and logged by the framework — they never affect the workflow or the request that triggered them.

## LLM request cost

### When events fire

An `cost:llm:request` event is emitted after every `generateText` and `streamText` call completes. For streaming, the event fires when the stream finishes, not when it starts.

### Payload

The handler receives an event envelope. The LLM usage attribute is available under `payload`:

```json theme={null}
{
  "eventId": "550e8400-e29b-41d4-a716-446655440000",
  "eventDate": 1780401600000,
  "activityInfo": {
    "activityId": "activity-1",
    "activityType": "generateSummary"
  },
  "workflowDetails": {
    "workflowId": "workflow-123",
    "runId": "run-123",
    "workflowType": "lead_enrichment"
  },
  "outputActivityKind": "step",
  "payload": {
    "type": "llm:usage",
    "modelId": "gpt-4o",
    "usage": [
      { "type": "input", "ppm": 5, "amount": 217, "total": 0.001085 },
      { "type": "output", "ppm": 15, "amount": 9, "total": 0.000135 }
    ],
    "total": 0.00122,
    "tokensUsed": 226
  }
}
```

| Field                | Type                  | Description                                                                                                                                              |
| -------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `eventId`            | `string`              | UUID v4 stamped per emit. Use as an idempotency key — `cost:llm:request` and `http:request` for the same fetch get distinct `eventId`s.                  |
| `eventDate`          | `number`              | Millisecond epoch timestamp for when the event was emitted.                                                                                              |
| `activityInfo`       | `object \| undefined` | Temporal [`activity.Info`](https://typescript.temporal.io/api/interfaces/activity.Info) when the call is made in an activity context.                    |
| `workflowDetails`    | `object \| undefined` | Output's serializable subset of Temporal [`workflow.WorkflowInfo`](https://typescript.temporal.io/api/interfaces/workflow.WorkflowInfo), when available. |
| `outputActivityKind` | `string \| undefined` | Output activity kind when available. Possible values are `step`, `evaluator`, and `internal_step`.                                                       |
| `payload.type`       | `"llm:usage"`         | Attribute type.                                                                                                                                          |
| `payload.modelId`    | `string`              | Model identifier (e.g. `claude-sonnet-4-20250514`, `gpt-4o`). From the prompt file config.                                                               |
| `payload.usage`      | `array`               | Priced usage entries for this call. See [Usage entries](#usage-entries).                                                                                 |
| `payload.total`      | `number`              | Total estimated cost in dollars.                                                                                                                         |
| `payload.tokensUsed` | `number`              | Total number of tokens represented by `payload.usage`.                                                                                                   |

### Usage entries

Cost is computed from the model's per-million-token pricing, fetched from a built-in pricing source and cached for 24 hours. For each priced dimension, the dollar amount is `(tokens / 1_000_000) * pricePerMillion`. Non-cached input tokens use `inputTokens - (cachedInputTokens ?? 0)`.

| Field    | Type     | Description                                  |
| -------- | -------- | -------------------------------------------- |
| `type`   | `string` | Usage dimension.                             |
| `ppm`    | `number` | Price per million tokens for this dimension. |
| `amount` | `number` | Token count for this dimension.              |
| `total`  | `number` | Cost for this dimension.                     |

`payload.usage[].type` values:

| `type`         | Description                                                               |
| -------------- | ------------------------------------------------------------------------- |
| `input`        | Non-cached prompt tokens.                                                 |
| `input_cached` | Cached prompt read tokens (`cachedInputTokens`).                          |
| `output`       | Completion tokens.                                                        |
| `reasoning`    | Reasoning tokens, only when the model defines separate reasoning pricing. |

Only available, finite usage dimensions are included. For example, `reasoning` is omitted when the model does not define separate reasoning pricing, and `input_cached` is omitted when there are no cached input tokens.

## HTTP request cost

Events fire only when your code calls [`addRequestCost( response, total )`](/packages/http#attaching-request-cost) with a **response object created by `@outputai/http`** (or its exported `fetch`). The SDK attaches the cost to the existing HTTP trace event and emits `cost:http:request`. If the response did not originate from this package, `addRequestCost` no-ops (with a console warning) and **no** hook event is emitted.

### Payload

The handler receives an event envelope. The HTTP request cost attribute is available under `payload`:

```json theme={null}
{
  "eventId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "eventDate": 1780401600000,
  "activityInfo": {
    "activityId": "activity-1",
    "activityType": "searchVendors"
  },
  "workflowDetails": {
    "workflowId": "workflow-123",
    "runId": "run-123",
    "workflowType": "lead_enrichment"
  },
  "outputActivityKind": "step",
  "payload": {
    "type": "http:request:cost",
    "requestId": "req-123",
    "url": "https://api.vendor.com/search",
    "total": 0.42
  }
}
```

| Field                | Type                  | Description                                                                                                                                                                                       |
| -------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `eventId`            | `string`              | UUID v4 stamped per emit. Use as an idempotency key — `cost:http:request` and `http:request` for the same fetch get distinct `eventId`s, so consumers keying by `eventId` won't collapse the two. |
| `eventDate`          | `number`              | Millisecond epoch timestamp for when the event was emitted.                                                                                                                                       |
| `activityInfo`       | `object \| undefined` | Temporal [`activity.Info`](https://typescript.temporal.io/api/interfaces/activity.Info) when the request is made in an activity context.                                                          |
| `workflowDetails`    | `object \| undefined` | Output's serializable subset of Temporal [`workflow.WorkflowInfo`](https://typescript.temporal.io/api/interfaces/workflow.WorkflowInfo), when available.                                          |
| `outputActivityKind` | `string \| undefined` | Output activity kind when available. Possible values are `step`, `evaluator`, and `internal_step`.                                                                                                |
| `payload.type`       | `"http:request:cost"` | Attribute type.                                                                                                                                                                                   |
| `payload.requestId`  | `string`              | Internal id linking this payload to the HTTP trace event for that request.                                                                                                                        |
| `payload.url`        | `string`              | Final response URL (same as `response.url`).                                                                                                                                                      |
| `payload.total`      | `number`              | Dollar cost passed to `addRequestCost`.                                                                                                                                                           |
