Verification Checklist

  • ✓Open this response. If content is empty and stop_reason is refusal, read stop_details.category. On the refusal doc's "billed before any output" column, bio, frontier_llm, and reasoning_extraction are charged. cyber, general_harms, and null are not. Then check whether this call still consumed a rate-limit slot.
  • ✓If text already streamed and refusal arrived after it, bill input plus output already streamed, and discard the partial. Do not file it as a finished delivery.
  • ✓On a retry you build yourself, look for fallback_credit_token. Message Batch refusals and Claude Haiku 5.5 refusals do not mint one. Without it, the next call pays a full prompt-cache write on the fallback model.
  • ✓On OpenAI, if stream_options.include_usage is on and the stream dies before [DONE], do not record the missing usage chunk as zero. Check the usage dashboard for that window. finish_reason: content_filter means the filter omitted content. It is not an HTTP error.
  • ✓A resume after a network cut is a new request. Tool-use blocks and thinking blocks cannot be recovered halfway. Do not file that call on the training-job cancel ledger.

1. Three categories bill before any output

Refusals and fallback prints the pre-output bill as a table and says the rules apply on every platform. When a classifier declines before any output, a stop_details.category of bio, frontier_llm, or reasoning_extraction is billed like any other request, at the rates of the model that ran. The page dates that choice to categories where Anthropic measured low false-positive volume, as of September 2026, so that large-scale attempts to route around safeguards still cost something. cyber, general_harms, and a null category are not billed before output. In both cases content is empty and token counts still appear in usage. On the free column those counts are not an invoice. On the billed column the same field is the usage. Either way the request still counts against rate limits.

Null is not "unclassified, wait for a label." The doc says category and explanation are both null when the refusal maps to no named category, and that null is a normal, permanent value. File a null with the categories that are not billed. Do not hold the ledger open for a name that will not arrive. The billed set can change as the false-positive measurement changes. Read the "billed before any output" column on the day you reconcile. Do not freeze today's three rows as a price list.

reasoning_extraction usually comes from a prompt that asks the model to put its thinking in the answer: a scratchpad filled before the reply, a reasoning field in JSON, a verbatim trace. The wording can sit in a system prompt, a skill, or a tool description. This category has no recommended fallback model. The doc says change the prompt rather than retry the same request. To see how the model reasoned, set thinking display to summarized and read the thinking blocks. That is a different meter from reasoning tokens that bill as output while staying invisible. There, internal reasoning is priced as output. Here, the classifier refuses because the prompt asked for that reasoning in the answer text, and the refusal can bill before a single output token.

2. Mid-stream bills what already left

The same page says a refusal can arrive before any output, or mid-stream after partial output. In either case, treat the partial as incomplete and discard it. A mid-stream refusal bills the input tokens and the output already streamed, at normal rates. A sentence that appears and then turns into a refusal is not "undelivered, so free." It is "cut off, and the part that left is paid."

A streaming refusal is still a successful HTTP 200, with stop_reason refusal and stop_details on the message_delta event. An alert that only watches 4xx and 5xx stays quiet. In Message Batches, a refused request comes back as result.type: succeeded with the same stop reason. Looking for it in the error file is the wrong file. Batch refusals also mint no fallback credit, so stop_details never includes a fallback_credit_token.

3. Credit does not void the refusal

When you use fallback, the refusal that triggered it is billed in addition to the fallback request, if it arrived mid-stream or sits in a category that bills before output. Fallback credit compensates for the fallback request's prompt-cache miss, so the conversation is not cached twice. It does not turn the first refusal into a free call. A Claude Haiku 5.5 refusal carries no credit, so a fallback after one pays the full prompt-cache write on the fallback model. Server-side fallback is in beta on the Claude API and on Anthropic-managed Claude models on Google Cloud. The fallbacks parameter is not supported on Message Batches; a batch item that includes it comes back as an errored result. Haiku 5.5 has no server-side fallback either. With fallbacks set to default, a declined request stays declined. A list of fallback models returns a 400.

An open tool call splits the rule again. A streaming refusal that fires while a tool-use block is still open — a client tool, a server tool, or an MCP call — is returned directly and is not retried. If the request sent the fallback-credit-2026-07-01 header, the credit token is still there, and it is redeemable only by continuing the partial response. Non-streaming requests are unaffected: the API clears the partial work and retries before it responds. If you build the retry yourself and the token has expired, or it can be redeemed only by continuing, dropping the token and running again re-bills server tools that already finished. Surface that cost. Do not retry it silently.

4. A missing usage chunk is not a refund

The OpenAI Chat Completions reference puts a note on stream_options.include_usage that is easy to read backwards. If the stream is interrupted, you may not receive the final usage chunk, the one that carries total token usage for the request. Usage on the other chunks is null. A client that records "no final chunk" as zero will not match the usage dashboard. A missing chunk is a broken stream. The reference does not call it a free request. Reconcile against the dashboard for that window, not against a sum the client never finished.

The same reference defines finish_reason content_filter as content omitted because of a flag from the content filters. That is a finish reason on the choice. The request can already have completed a choice. It is not an HTTP error code. Mapping a Claude refusal onto that field, or mapping that field onto "only three categories bill before output," mixes two ledgers. A canceled training job is a different article: a cancel bills tokens already used, and other interruptions do not bill training. A chat stream that dies does not land on that training table.

5. A resume is a new request

Streaming messages describes error recovery as a new request, not a rewind of the old meter. On Claude 4.5 and earlier, put the partial you already received into a new assistant message and continue. On Claude 4.6 and later, put it in a user message that says the previous response was interrupted and should continue from there. The doc says tool-use blocks and extended-thinking blocks cannot be partially recovered. Resume from the latest text block. The continuation therefore sends text you already paid to generate back in as input. That is the same shape as a short "continue" that still bills the chain: the new words are not the whole bill.

A top-up hits the account before any of these columns settle. Swapping a stablecoin into an asset the console can take does not move bio onto the unpaid list, does not turn an empty content array into "this call skipped rate limits," and does not erase output that already streamed. When that swap is the step you need, a non-custodial aggregator such as AllSwap can do it without an account. A completed swap only means the asset changed hands. stop_details.category and the tokens already streamed still follow this request's own rules.