Usage object

OpenAI usage shape plus cached_prompt_tokens, and the /v1/usage quota endpoint.
Every chat completion includes a usage object in OpenAI’s shape. It carries both OpenAI’s nested cache field and a Malibu field that tracks billing.

Shape

A live second turn from 23 Sep 2026 that sent a sticky conversation tag:
The two cache fields can differ. cached_tokens counts every prefix reuse. cached_prompt_tokens counts only reuse that earned the discount. The object may also include a model-hash diagnostic field. Ignore fields you don’t use.

prompt_tokens_details.cached_tokens (observed reuse)

This is the field OpenAI-compatible clients read as cache reads. The gateway derives a prefix-cache key automatically for authenticated requests, from the system prompt and tools, or from the messages up to the first user turn. Repeated scaffolds can therefore hit a provider’s cache without any header. These automatic hits show up here and are billed at the full prompt rate.

cached_prompt_tokens (discounted reuse)

Non-zero when all of these hold:
  • The request sent X-MacProvider-Conversation.
  • It routed to the same provider as an earlier turn with that tag, within the sticky TTL (currently 1,800 seconds).
  • The provider reused a shared prefix.
It is 0 when there is no tag, when the sticky provider was unavailable and the request routed elsewhere, or when the prefix did not match. The field is always present, so you can read it without checking first.

Billing

Rate-card rows carry prompt_rate_per_mtok, completion_rate_per_mtok, and prompt_cache_hit_rate_per_mtok. Tokens in cached_prompt_tokens meter at the cache-hit rate. The rest of the prompt meters at the prompt rate. See Metering & billing for the formula and Sticky conversations for how to opt in.

Reading from the SDK

cached_prompt_tokens is not a typed attribute in openai-python. The SDK keeps unknown fields, so getattr (or usage.model_extra) reads it. For streaming, the last chunk before [DONE] always carries the same usage object. You don’t need to set stream_options.include_usage.

Account quota: GET /v1/usage

Today’s quota state for your account, for the current UTC day only:
The response also lists your API keys by prefix and status (never the full key) and includes a settlement_disclosure block. The endpoint takes no date-range parameters and returns no per-day history. The same quota numbers come back on every chat response as X-RateLimit-* headers. When the daily quota runs out, chat requests return 429 quota_exhausted until X-RateLimit-Reset (UTC midnight).