← Blog

Claude API Streaming: How to Cancel a Response Mid-Stream

2026-10-24 · 4 min read · SubToAPI Team

Can you cancel a Claude API streaming request?

Yes. When you stream a response from the Claude API, the request stays open as an HTTP connection (typically Server-Sent Events) until the model finishes generating or you close the connection yourself. Canceling is a client-side operation: you abort the underlying HTTP request, the server stops sending tokens, and Claude stops generating for that request. There's no separate "cancel" endpoint to call — you just close the stream.

This matters in a few common real-world scenarios: a user navigates away from a chat UI mid-response, clicks a "stop generating" button, or you have a race condition where you kick off a second request and want to kill the first. In all of these cases, the fix is the same pattern used across modern fetch-based APIs: AbortController on the client, and proper cleanup on your server if you're proxying the stream.

Why cancellation matters for streaming

Streaming responses are long-lived connections. If you don't cancel them properly:

Getting cancellation right is not optional for production chat UIs. It's the difference between a responsive "stop" button and a backend quietly accumulating zombie connections.

Canceling with AbortController (Node.js / browser)

The standard mechanism in modern JavaScript is AbortController. You create a controller, pass its signal into your fetch call, and call .abort() whenever you want to stop.

const controller = new AbortController();

async function streamResponse(prompt) {
  const res = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "x-api-key": process.env.ANTHROPIC_API_KEY,
      "anthropic-version": "2023-06-01",
      "content-type": "application/json",
    },
    body: JSON.stringify({
      model: "claude-3-5-sonnet-20241022",
      max_tokens: 1024,
      stream: true,
      messages: [{ role: "user", content: prompt }],
    }),
    signal: controller.signal,
  });

  const reader = res.body.getReader();
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    process.stdout.write(new TextDecoder().decode(value));
  }
}

// Call this from a "stop" button handler
function stopGenerating() {
  controller.abort();
}

When abort() is called, the fetch promise rejects with an AbortError, the reader's read() call throws, and the underlying TCP connection closes. You should wrap the read loop in a try/catch to handle the abort cleanly instead of letting it bubble up as an unhandled rejection:

try {
  await streamResponse(prompt);
} catch (err) {
  if (err.name === "AbortError") {
    console.log("Stream canceled by user");
  } else {
    throw err;
  }
}

Canceling in Python with httpx

If you're streaming server-side with httpx, you cancel by exiting the streaming context manager or canceling the enclosing asyncio task:

import httpx
import asyncio

async def stream_and_cancel(prompt, cancel_event):
    async with httpx.AsyncClient() as client:
        async with client.stream(
            "POST",
            "https://api.anthropic.com/v1/messages",
            headers={"x-api-key": "...", "anthropic-version": "2023-06-01"},
            json={
                "model": "claude-3-5-sonnet-20241022",
                "max_tokens": 1024,
                "stream": True,
                "messages": [{"role": "user", "content": prompt}],
            },
        ) as response:
            async for chunk in response.aiter_bytes():
                if cancel_event.is_set():
                    break  # exits the context manager, closes the connection
                print(chunk)

Breaking out of the async for loop while inside the async with block closes the HTTP connection on the way out, which is functionally the same as aborting on the client side.

Server-side proxy pattern: forwarding cancellation

If your frontend talks to your own backend, which in turn streams from Claude, you need to propagate the cancellation in both directions. The common pattern:

  1. Browser sends a request with its own AbortController signal to your backend.
  2. If the browser aborts (tab closed, button clicked), your backend's request handler should detect the client disconnect (e.g., Express's req.on("close")).
  3. Your backend then aborts its own outbound fetch to Claude using a second AbortController.
app.post("/chat", async (req, res) => {
  const upstreamController = new AbortController();

  req.on("close", () => {
    upstreamController.abort();
  });

  const upstream = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    signal: upstreamController.signal,
    // ...headers, body
  });

  upstream.body.pipe(res);
});

Without this chain, closing the browser tab doesn't actually stop Claude from generating — your backend will keep consuming (and paying for) tokens even though nobody's listening.

How SubToAPI handles this

If you're using SubToAPI (https://subtoapi.app) to expose your Claude access as an HTTPS API with sub_live_... keys, the same AbortController pattern applies directly — SubToAPI's streaming endpoint is a standard SSE stream, so aborting the fetch on your side closes the connection and stops billing for that request immediately. See /docs/streaming for the exact request shape and /docs/messages for the non-streaming equivalent if you'd rather poll for completion. If you're just getting started, /docs/quickstart walks through generating your first key and making a request; pricing details for Solo, Team, and Scale plans are at /pricing.

The key point: cancellation is always a connection-close operation, whether you're calling Claude directly or through a proxy like SubToAPI. There's no cancel token or cancel API call to learn — just abort the HTTP request.

FAQ

Does canceling a stream stop token billing immediately? Yes, in practice. Once the connection closes, the model stops generating and you're only billed for tokens produced up to that point, not for the full response you would have received.

Can I cancel just one request if I have multiple streams open? Yes, as long as each request has its own AbortController (or equivalent). Calling abort() on one controller only closes that specific connection, leaving other concurrent streams unaffected.

What happens to partial output after I cancel? Whatever tokens were already sent to your client before cancellation remain in your buffer — Claude doesn't retract them. You decide whether to discard, display, or save that partial response in your application logic.

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 →