Retrieve LLM Token Usage in AI Agents

I actually solved the problem by adapting @solomon‘s solution for my case.

What I did was simply adding two nodes right after the Agent node (Edit Fields > HTTP request, bcs I was having some trouble with the Execute Sub-workflow node), as you can see below:

The Edit Fields node just filters the execution_id at the active workflow using {{ $execution.id }}.

The HTTP request node does a POST to {your_n8n_url}/webhook/log-tokens, with the following JSON body (in MY case, I also send together workspace_id and instance bcs these are UUIDs for my SaaS backend, for billing):

{
  "execution_id": "{{ $json.execution_id }}",
  "workspace_id": "...",
  "instance": "..."
}

BUT you’ll probably use only:

{
  "execution_id": "{{ $json.execution_id }}"
}

Then I created another workflow:

Basically, it has a Webhook trigger, that listens to the POST request did at the last workflow, listening path "log-tokens”:

Then I added a Wait node of 5 secs just to make sure the workflow is over. Then Get an execution node:

By this point, I already had the AI Agent tokenUsage, then I just used and edited the Edit Fields node made by @solomon after that to get the execution_id in Webhook, and to filter whatever else I needed for my implementation:

I also kept the Split Out intact as in the original as in @solomon’s solution.

Then I noticed the output value delivered by n8n is somehow completely inflated. After testing and checking directly at OpenAI Platform, I realized that specifically for OpenAI models there’s a ~1.6x increase in token usage in n8n in relation to the number displayed at the official OpenAI API data (and it’s actually what’s used for billing).

Also, in my case, it’s interesting to convert currency, as my country, SaaS etc. don’t use USD.

Then I used a Code node with the following script, for reasoning this discrepancy and converting USD to BRL (approximately, of course, using a slightly pessimistic scenario):

// --- CONFIGURAÇÃO ---
const CORRECTION_FACTOR = 0.60;
const USD_TO_BRL = 6.00; 
const PRICING = { input: 0.25, output: 2.00 };

const item = $input.item.json;

// --- FUNÇÃO DE RESGATE SEGURO ---
// Tenta pegar o valor de todas as formas possíveis que o n8n costuma entregar
function getValue(obj, pathString) {
  // Tentativa 1: Acesso direto por string (ex: json["tokenUsage.promptTokens"])
  if (obj[pathString] !== undefined) return obj[pathString];
  
  // Tentativa 2: Navegação aninhada (ex: json.tokenUsage.promptTokens)
  const keys = pathString.split('.');
  let value = obj;
  for (const key of keys) {
    if (value && value[key] !== undefined) {
      value = value[key];
    } else {
      return undefined; // Não achou
    }
  }
  return value;
}

// 1. Busca os valores (Tenta aninhado ou plano)
let rawPrompt = getValue(item, 'tokenUsage.tokenUsage.promptTokens') || getValue(item, 'tokenUsage.promptTokens') || 0;
let rawCompletion = getValue(item, 'tokenUsage.tokenUsage.completionTokens') || getValue(item, 'tokenUsage.completionTokens') || 0;

// 2. DEBUG DE EMERGÊNCIA
// if (rawPrompt === 0) return { json: { erro: "Não achei os tokens", estrutura_recebida: item } };

// 3. Aplica a correção
const realPrompt = Math.ceil(rawPrompt * CORRECTION_FACTOR);
const realCompletion = Math.ceil(rawCompletion * CORRECTION_FACTOR);
const realTotal = realPrompt + realCompletion;

// 4. Calcula custo
const costInputUSD = (realPrompt / 1000000) * PRICING.input;
const costOutputUSD = (realCompletion / 1000000) * PRICING.output;
const totalUSD = costInputUSD + costOutputUSD;

return {
  json: {
    execution_id: item.execution_id,
    workspace_id: item.workspace_id,
    instance: item.instance,
    // Tenta pegar o modelo, se falhar, usa fallback
    model: getValue(item, 'tokenUsage.model') || 'gpt-5-mini', 
    
    usage: {
      prompt_tokens: realPrompt,
      completion_tokens: realCompletion,
      total_tokens: realTotal
    },
    
    costs: {
      usd: Number(totalUSD.toFixed(6)),
      brl: Number((totalUSD * USD_TO_BRL).toFixed(4))
    },
    
    audit: {
      raw_found: rawPrompt + rawCompletion,
      original_source: rawPrompt > 0 ? "found_data" : "zero_data_error"
    }
  }
};

If you’re afraid of the reasoning being too strict, you can set the value 0.60 to 0.65 or 0.70 at CORRECTION_FACTOR. Also, you’ll have to change PRICING based on the exact cost per million tokens of the model you’re using. In my case, it’s gpt-5-mini, so its $0.25 input / $2.00 output.

And, of course, if you don’t want any currency conversion (or another conversion for another currency), you’ll have to adapt the code a bit, but as the code also delivers the USD value spent, I think it’s ready to use even in this case.

Well, after that, I just use another regular HTTP request to POST the collected data into my SaaS backend via API. But, at this point, you can send wherever you want, or even use another type of node, like Sheets, to send data.

1 Like