Skip to main content
You may need to backfill Langfuse costs when a generation records token usage without a dollar cost. This usually happens when the model is new, its name is an alias, or it doesn’t match a Langfuse model definition. You can repair those observations without recreating their traces. Read each generation from Langfuse, calculate its cost with Narev, then update the original observation by ID. The script at the end of this guide automates the process, but the steps below show what it does first.

Before you start

You need Node.js 22 or later and a Langfuse public and secret key. The Narev pricing endpoints in this guide are public, so you don’t need a Narev API key. Choose a bounded time range for the backfill. Smaller ranges are easier to review and retry. They also prevent the script from scanning your complete Langfuse history.
The start time is inclusive and the end time is exclusive. The example covers July 2026.

1. Find generations without costs

Start with the Langfuse Observations API v2. Ask for generation observations and include the model and usage field groups. You don’t need prompts, outputs, or metadata to calculate cost.
Each generation eligible for backfill has an id, a traceId, a providedModelName, and usageDetails. Ignore observations that already have costDetails.total or totalCost. Langfuse returns a cursor in meta.cursor when another page exists. Send that cursor with the next request and continue until it returns null. The complete script handles this loop, so a backfill isn’t limited to the first page.

2. Translate the token usage

Narev needs prompt, completion, cache, and reasoning token counts. Langfuse stores the same information in named buckets inside usageDetails, but the names depend on the integration that created the observation. For a typical Langfuse observation, map input to prompt_tokens and output to completion_tokens. Map cache_read_input_tokens and cache_write_input_tokens to their corresponding Narev cache fields. If the observation separates reasoning tokens into output_reasoning_tokens, send them as reasoning_tokens. The important rule is to count each token once. Langfuse treats these values as separate buckets. If your integration includes cached tokens in input, subtract them before sending the prompt count to Narev.
Inspect a few real usageDetails objects before running the backfill. Update the script’s mapUsage function if your integration uses different field names.

3. Match the model to a price

Use the model name from providedModelName to search Narev:
When the search returns one exact model ID from one provider, the script accepts that result and caches it for later observations. Some names need an explicit decision. A gateway alias may not match a Narev model ID, and the same model may be sold by several providers. Add those names to modelOverrides in the complete script. This keeps an ambiguous match from producing the wrong cost.

4. Calculate and write the cost

Send the resolved model, provider, and token counts to the Narev cost endpoint:
Narev returns the dollar amount in cost_breakdown.total. Write that value to the existing generation with a Langfuse generation-update event. Reuse the observation’s id and traceId; only the event envelope gets a new UUID. Langfuse responds to ingestion requests with HTTP 207, including successful requests. You still need to inspect the response’s errors array. The script checks both the HTTP response and each event result before it reports an update as successful.
Langfuse has deprecated this ingestion endpoint for new traces in favor of OpenTelemetry. This guide uses it because it can update an existing generation. Review the Langfuse ingestion API before putting the backfill on a schedule.

5. Run the backfill

The script combines the four stages above. It pages through the selected time range, skips observations that already have costs, and handles one observation at a time. A bad model alias or unfamiliar usage shape doesn’t stop the rest of the run. Dry-run mode is enabled by default. In that mode, the script calculates and prints costs without changing Langfuse. Expand the code only when you’re ready to save it as backfill-langfuse-costs.mjs.
backfill-langfuse-costs.mjs
Run a dry run first:
Review the calculated costs and skipped observations. Then allow writes:
A successful write prints Updated <observation-id>: $<cost>. Open that observation in Langfuse and confirm that its total cost matches the script output.

Troubleshooting

A model couldn’t be matched

Expected one Narev price means the search found no exact price or found the model under several providers. Add the Langfuse model name and your chosen Narev model and provider to modelOverrides.

The usage shape isn’t supported

Unsupported usageDetails means your Langfuse integration uses token field names that mapUsage doesn’t recognize. Inspect the observation, add the relevant keys, and confirm that prompt, cache, and reasoning tokens don’t overlap.

Langfuse rejected an update

Read the event error returned with the HTTP 207 response. Confirm that the observation ID and trace ID came from the same generation and that the API keys belong to its Langfuse project.

The script found nothing to update

Check the UTC date range first. If the range is correct, inspect one expected generation and confirm that it has usageDetails but doesn’t already have costDetails.total.

The calculated cost is too high

Check whether the input bucket includes cached tokens. If it does, subtract the cached count before sending prompt_tokens; otherwise, the script charges those tokens twice.