How much each call costs
The Energy cost of each execution, of a Run, of a Met or of a WhatsApp line — with filters, totals and consumption grouped by line.
Every time a Met works —answers a WhatsApp message, runs a Run you started through the API, executes an automation— Energy is debited from your balance. This guide shows how to find out how much each call cost and how to add it up per Run, per Met or per WhatsApp line.
Everything in this guide is read-only and needs the billing:read scope (a Run's cost, runs:read). Amounts are the Energy debited to you, in USD: the same figure you see in the dashboard.
Each execution, with its origin
GET /billing/executions lists your workspace's executions, newest first. Each one says how much it cost and where it came from:
| Field | What it says |
|---|---|
cost_final_usd | The Energy debited for this execution, in USD. |
met_id, met_name | The Met that did the work. |
model_display | The intelligence level it used (Ágil, Brillante, Genio…). |
run_id | The API Run that caused it (run_…), or null if it did not come from a Run. |
channel | Where the work came in: whatsapp, instagram, messenger, widget, panel, api… |
whatsapp_line | The WhatsApp line: { id, number_masked }, or null (see what carries a line). |
whatsapp_line.id is Meta's phone_number_id, the same value you use in sender_line_id and that GET /channels/{channelId}/whatsapp/lines returns. The number is masked (+57 300 ••• 4567): enough to recognize the line without copying the full number into every row.
import Met from '@meteor.ia/sdk';
const met = new Met(process.env.MET_API_KEY, { workspaceId: 7 });
const page = await met.billing.executions({ limit: 20 });
for (const e of page.rows) {
console.log(e.executed_at, e.met_name, e.channel, e.whatsapp_line?.number_masked ?? '-', e.cost_final_usd);
}
console.log('Filter total:', page.totals?.cost_final_usd);import os
from meteor_ia import Met
met = Met(os.environ["MET_API_KEY"], workspace_id=7)
page = met.billing.executions(limit=20)
for e in page["rows"]:
line = (e.get("whatsapp_line") or {}).get("number_masked", "-")
print(e["executed_at"], e["met_name"], e["channel"], line, e["cost_final_usd"])
print("Filter total:", (page.get("totals") or {}).get("cost_final_usd"))curl "https://api.met.meteor.com.co/api/v1/billing/executions?limit=20" \
-H "Authorization: Bearer $MET_API_KEY"The response is a page { rows, total, limit, offset, totals }. The maximum is 100 per page: a larger limit is capped at 100 without an error, and the response's limit is the one applied. totals adds up all executions matching the filter, not just the ones on the page.
What never comes out: the provider's cost, the margin or the model name. Neither do the contact or the conversation: to see spend per contact, use GET /billing/consumption?by=contact.
Filter by Met, line or Run
Three filters answer "how much did this cost me?":
| Filter | Example | What it returns |
|---|---|---|
met_id | met_id=41207 | Only what that Met did. |
line_id | line_id=1210946622112871 | Only what came in through that WhatsApp line. |
run_id | run_id=run_01JB8ZQ4… | Only what that Run caused. |
They combine with each other and with from, to, type, costMin and costMax. totals honors the same filters, so a single call gives you the total:
// How much did the sales line cost in October?
const october = await met.billing.executions({
line_id: '1210946622112871',
from: '2026-10-01T00:00:00Z',
to: '2026-10-31T23:59:59Z',
limit: 1,
});
console.log(october.total, 'executions ·', october.totals?.cost_final_usd, 'USD');october = met.billing.executions(
line_id="1210946622112871",
from_="2026-10-01T00:00:00Z",
to="2026-10-31T23:59:59Z",
limit=1,
)
print(october["total"], "executions ·", october["totals"]["cost_final_usd"], "USD")curl "https://api.met.meteor.com.co/api/v1/billing/executions?line_id=1210946622112871&from=2026-10-01T00:00:00Z&to=2026-10-31T23:59:59Z&limit=1" \
-H "Authorization: Bearer $MET_API_KEY"A malformed filter returns 400, with the parameter in param (invalid_met_id, invalid_line_id, invalid_run_id): it is never silently ignored, because returning every execution would make you believe that line spent the whole account's Energy. line_id is the phone_number_id (digits only), not the phone number.
To walk every execution of a filter without handling offset:
let sum = 0;
for await (const e of met.billing.iterateExecutions({ met_id: 41207, from: '2026-10-01T00:00:00Z' })) {
sum += e.cost_final_usd;
}total = sum(e["cost_final_usd"] for e in met.billing.iterate_executions(met_id=41207, from_="2026-10-01T00:00:00Z"))What a Run cost
GET /runs/{id} includes cost_final_usd: what that Run cost, adding up the Energy of every execution it caused (the same ones as GET /billing/executions?run_id=…).
const run = await met.runs.create({ input: 'Summarize today\'s leads' });
const detail = await met.runs.retrieve(run.id);
console.log(detail.status, detail.cost_final_usd);run = met.runs.create("Summarize today's leads")
detail = met.runs.retrieve(run["id"])
print(detail["status"], detail.get("cost_final_usd"))curl https://api.met.meteor.com.co/api/v1/runs/run_01JB8ZQ4W8X3K7M2N5P6R9T0VA \
-H "Authorization: Bearer $MET_API_KEY"Three things to read it right:
- Only in the detail. The
POST /runsresponse and the list don't include it: the cost settles after the Run answers. - It can grow for a few minutes. Whatever the Run left running when it answered (a summary, an evaluation) is charged a little later and also belongs to it. Ten minutes after
completed_atit is closed. nullis not zero. It isnullwhile the Run is still running, and when there are no executions recorded with its id: a Run from before version 3.51.0 of the contract or one that failed before reaching the Met. A test Run (met_test_) with executions gives0.
energy, already in the detail, adds up the Run's conversation: if you reused conversation_id, it includes other Runs. For the cost of a single one, use cost_final_usd.
Consumption grouped by line
GET /billing/consumption?by=line splits a period's Energy by WhatsApp line, from the one that spent the most to the least:
const byLine = await met.billing.consumption({ by: 'line', from: '2026-10-01T00:00:00Z' });
for (const l of byLine) {
console.log(l.whatsapp_line?.number_masked, l.executions, l.energia_usd);
}by_line = met.billing.consumption(by="line", **{"from": "2026-10-01T00:00:00Z"})
for l in by_line:
print(l["whatsapp_line"]["number_masked"], l["executions"], l["energia_usd"])curl "https://api.met.meteor.com.co/api/v1/billing/consumption?by=line&from=2026-10-01T00:00:00Z" \
-H "Authorization: Bearer $MET_API_KEY"Each row has energia_usd, executions, time_saved_minutes and whatsapp_line. Use whatsapp_line.id to drill down with line_id; dim_key is the line's internal id in Met (null if the line is no longer connected to your account). If a line was disconnected, its consumption still shows up, with number_masked as null.
The same endpoint groups by met (default), contact, actor (team member) and conversation.
What carries a line, and since when
Met records each execution's Run and line since version 3.51.0 of the contract. Older data is not rebuilt: those executions have run_id and whatsapp_line as null and don't count in line_id, run_id or by=line. In old executions, channel may say whatsapp for any channel. The cost (cost_final_usd) is complete from the start.
These carry a line:
- the Met's answer to a message that came in through WhatsApp, with everything it did to answer it;
- the follow-ups and synthetic events of that conversation;
- flows started by a WhatsApp message (their Met nodes);
- the transcription of voice notes that arrive through WhatsApp.
These don't carry a line, even when the spend is WhatsApp's:
- manually re-invoking the Met on a conversation;
- summaries that run outside an answer;
- background jobs;
- everything before version 3.51.0.
That is why whatsapp_line can be null on WhatsApp spend, and the by=line total can be lower than what the whole channel spent. For the channel's total, add up cost_final_usd of the executions whose channel is whatsapp.