ModelBeatbeta

Costs

The usage.cost breakdown, how the prepaid hold works, and why a request can be refused with 402.

Every successful response tells you what it cost:

"usage": {
  "prompt_tokens": 18,
  "completion_tokens": 42,
  "total_tokens": 60,
  "cost": {
    "input_tokens_cost": 0.00009,
    "output_tokens_cost": 0.00042,
    "total_cost": 0.00051
  }
}

All figures are USD. total_cost is what came off your prepaid balance.

Fields

FieldMeaning
input_tokens_costCost of the prompt tokens.
output_tokens_costCost of the generated tokens.
reasoning_tokens_costCost of reasoning tokens, on models that bill them separately.
citation_tokens_costCost of citation tokens, on models that produce them.
search_queries_costCost of provider-side search queries, where applicable.
request_costAny flat per-request charge.
total_costThe sum. This is the authoritative number.

cost is present when the serving provider supports cost calculation. usage itself can be null when a provider reported no usage at all, so guard for that before reading cost.

Reserve, then settle

ModelBeat is prepaid, so it cannot discover after the fact that you could not afford a call. For each request it:

  1. computes the worst-case cost, bounded by the specific models that could serve this request;
  2. reserves that amount against your balance;
  3. routes and executes;
  4. settles the reservation against realized usage, releasing the difference.

You are charged what the call actually cost, not the hold. The hold exists only for the duration of the request.

Why you might see 402

{ "error": "modelbeat: insufficient balance" }

The hold exceeded your available balance, which is not necessarily the final cost. Because the hold is a worst case, a large max_tokens can be refused even when the call would have been cheap in practice.

Two ways out:

  • Top up. Obvious, and correct if your usage is growing.
  • Lower max_tokens. This directly shrinks the hold. If you know a reply will be short, saying so lets a thinner balance through.

Reconciling

For per-request accounting, store usage.cost.total_cost with the X-Request-Id from the same response. That pair is enough to reconcile your records against your ModelBeat usage report line by line.

# with_raw_response, because the request id is a HEADER. It is not in the body.
raw = client.chat.completions.with_raw_response.create(
    model="auto", messages=[...], max_tokens=200
)
r = raw.parse()

# Fields the OpenAI client does not know about arrive as plain dicts, so these are
# subscripted rather than dotted. `r.usage.cost.total_cost` raises AttributeError.
if r.usage and r.usage.cost:
    ledger.record(
        request_id=raw.headers.get("x-request-id"),
        model=r.extra_fields["routing_info"]["model"],
        usd=r.usage.cost["total_cost"],
    )

Aggregate spend, per-key budgets, and usage history live in the console.

On this page