← Blog

Claude API Partial Response Cancellation Guide

2026-10-12 · 5 min read · SubToAPI Team

When a user stops reading, closes a tab, or clicks "stop generating" halfway through a long Claude response, you need a way to cancel that request before it finishes — without leaking connections, wasting tokens, or leaving your UI in a broken state. This is partial response cancellation: terminating a streaming Claude API call mid-generation while handling the already-received tokens correctly.

The short answer: Claude's API doesn't have a dedicated "cancel" endpoint. Cancellation happens on the client side by aborting the HTTP connection (via AbortController in JavaScript, or closing the socket in other languages). Anthropic stops generating tokens once it detects the connection is closed, and you're typically only billed for tokens actually generated up to that point, not the full response you would have received. The rest of this article covers how to implement that correctly, what edge cases to watch for, and how billing and partial-content handling work in practice.

Why Cancellation Matters

Long-form Claude responses — multi-paragraph explanations, generated code, structured reports — can take 10-30 seconds or more to fully stream. Without cancellation support:

Supporting cancellation is a correctness issue as much as a UX one, especially if you're running many concurrent streams per server.

How Cancellation Actually Works

Claude's streaming API (Server-Sent Events over HTTP) generates tokens and pushes them down an open connection. There's no "stop job #123" API call. Instead:

  1. You start a streaming request.
  2. You keep a reference to the thing controlling that connection (an AbortController, a cancellable fetch, a Python request with a timeout/close hook).
  3. When the user wants to stop, you call .abort() or close the underlying socket.
  4. The server sees the client disconnect and stops generating further tokens for that request.

This means cancellation is fundamentally a client-initiated connection close, not a server-side cancel command.

JavaScript Example (Browser/Node)

const controller = new AbortController();

async function askClaude(prompt) {
  const response = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${SUBTOAPI_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "claude-sonnet-4",
      max_tokens: 1024,
      stream: true,
      messages: [{ role: "user", content: prompt }]
    }),
    signal: controller.signal
  });

  const reader = response.body.getReader();
  let partialText = "";

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      partialText += new TextDecoder().decode(value);
      // update UI incrementally here
    }
  } catch (err) {
    if (err.name === "AbortError") {
      console.log("Stream cancelled. Partial text so far:", partialText);
    } else {
      throw err;
    }
  }
}

// Call this when the user clicks "Stop"
function cancelGeneration() {
  controller.abort();
}

The key detail: wrap your stream-reading loop in a try/catch and check for AbortError specifically. Don't let the abort surface as an unhandled exception — it's an expected event, not a failure.

Server-Side Proxies

If your frontend talks to your own backend, which in turn calls the Claude API, you need to propagate cancellation across both hops. When the frontend disconnects (e.g. closes the SSE connection to your server), your server needs to detect that and abort its own upstream request to Claude. In Node.js with Express, listen for the close event on the incoming request and call .abort() on the outgoing fetch's controller. Skipping this step is the most common bug: the frontend gives up, but the backend keeps burning tokens because nothing told it to stop.

What Happens to Billing on a Cancelled Stream

You're generally billed for the output tokens actually generated and streamed to you before cancellation, not the full response that would have completed. If you cancel after 200 tokens of a potential 800-token response, you're charged for roughly 200 tokens of output, plus your input tokens as normal. This is one of the practical reasons to implement cancellation properly — it directly reduces API spend on abandoned requests, especially in chat UIs where users frequently interrupt long answers.

If you're routing calls through SubToAPI, usage metadata returned with each request reflects actual tokens consumed, so cancelled streams show up accurately in your per-key and per-seat usage reporting rather than being counted as if they completed.

Handling Partial Content Correctly

A cancelled response isn't garbage — it's a legitimate partial result, and you should treat it as one:

Testing Cancellation Behavior

Before shipping, deliberately test:

  1. Cancelling within the first token (connection barely open)
  2. Cancelling mid-stream (hundreds of tokens in)
  3. Cancelling right as the final token arrives (race condition between "done" and "abort")
  4. Network drop vs. explicit user cancel (both should behave the same way client-side)

If you're building this on top of SubToAPI, the streaming behavior and SSE format follow the same conventions documented in /docs/streaming, so standard AbortController patterns work without modification — you don't need special handling for sub_live_ keys versus any other bearer token.

Quick Reference

Questions

Does cancelling a Claude API stream stop billing immediately? Billing reflects tokens actually generated before the connection closes. Once you abort, no further output tokens are produced or charged, though there may be a small amount of in-flight token generation between your abort signal and the server detecting it.

Can I cancel a non-streaming (single-response) Claude API call? Yes, in the same way — close the connection before the response completes. But since non-streaming calls return nothing until finished, there's no partial content to keep, so the main benefit is just freeing up the connection, not saving tokens mid-generation.

Why does my cancelled request still show as "completed" in logs? This usually means the abort signal didn't propagate past your frontend to the actual upstream request to Claude. Check that any backend proxy also aborts its own outbound call when the client disconnects, rather than letting it run to completion silently.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →