Stopping a client does not prove AI generation stopped. Separate connection abort, cancellation requests and confirmed outcomes before inferring cost.
REQUEST LIFECYCLE · BLOG 161
By Token360 · Editorial review: November 5, 2026
Clicking Stop does not, by itself, prove that an AI generation stopped. The button may hide output, abort a connection or submit a documented cancellation request. Those actions have different meanings. To cancel AI API generation reliably, identify the endpoint’s cancellation contract, retain the request or job reference, and distinguish the user’s intent from the confirmed outcome.
This question extends OpenRouter’s FAQ explanation of streaming: once an app displays output incrementally, what should happen when its user stops waiting? The answer depends on the actual interface and implementation, not on the presence of a Stop button.
We ran three local HTTP demonstrations to make the distinction visible. They used a simulated worker with an explicit cancellation handler and no model API, credentials or paid generation. The results are evidence about that fixture, not measurements of Token360, fal or OpenRouter.
Read first: Token360’s API selection guide helps identify the interface your application uses. This article covers cancellation behavior, rather than general streaming setup or asynchronous architecture.
What can a Stop button actually stop?
A Stop button can stop rendering, stop receiving data or request that a remote job stop executing. Determine which action your implementation performs. A local interface change establishes only local behavior unless it is connected to a supported cancellation mechanism and its result is checked. Use distinct labels for distinct outcomes.
MDN’s AbortController reference describes aborting fetch requests, response-body consumption and streams. It does not establish an arbitrary server worker’s lifecycle or a model provider’s billing policy. Those need their own contract.
Action
What the app knows
What remains unproven
Stop updating the screen
The user no longer sees new output.
Whether a connection or worker stopped.
Abort the client connection
The local request was interrupted.
Whether downstream work observed and honored that interruption.
Send a cancellation request
The application attempted the documented operation.
Whether it was accepted, completed or arrived too late.
Receive a documented terminal outcome
The service reports a final state under that contract.
Whether usage is zero or a refund applies.
In a browser-to-backend-to-provider application, identify every boundary. Stopping the browser’s fetch does not describe what the backend does with its separate upstream request. The backend might propagate cancellation, or it might retain the job. Inspect that code path instead of inferring behavior from the disappearing spinner.
What happened in the three local demonstrations?
The local fixture showed that client interruption and worker cancellation can diverge. Aborting the connection produced an AbortError while the simulated worker completed. Calling its explicit cancellation handler stopped the pending worker. A cancellation sent after completion left the completed state intact. These outcomes followed the fixture’s deliberately defined rules, not a provider-wide property.
Method: on November 5, 2026, we used Node.js v26.13.0 and a loopback HTTP server. A work request scheduled a 250-millisecond timer. The worker intentionally ignored connection closure; a separate cancel handler cleared a pending timer. Each case began only after the server recorded that work had started.
Executed local case
Client or cancel response
Final worker state
Interpretation
Abort connection only
AbortError
completed
Local cancellation was not wired to worker cancellation.
// Synthetic local demonstration only. No provider calls or credentials.
const http=require('node:http');
(async()=>{
const events=[],jobs=new Map();let serial=0;
const server=http.createServer((req,res)=>{
const u=new URL(req.url,'http://localhost');
if(u.pathname==='/work'){
const id=++serial;const job={id,state:'running'};jobs.set(id,job);
events.push({id,event:'started'});
res.on('close',()=>events.push({id,event:'connection_closed',state:job.state}));
job.timer=setTimeout(()=>{job.state='completed';events.push({id,event:'completed'});if(!res.destroyed)res.end(JSON.stringify({id,state:job.state}));},250);
}else if(u.pathname==='/cancel'){
const j=jobs.get(Number(u.searchParams.get('id')));
if(j&&j.state==='running'){clearTimeout(j.timer);j.state='cancelled';events.push({id:j.id,event:'cancelled'});res.end('cancelled');}
else res.end(j?.state||'unknown');
}else{res.statusCode=404;res.end();}
});
await new Promise(r=>server.listen(0,'127.0.0.1',r));
const base=`http://127.0.0.1:${server.address().port}`;
const pause=ms=>new Promise(r=>setTimeout(r,ms));
async function start(){const c=new AbortController();const before=serial;const pending=fetch(base+'/work',{signal:c.signal}).then(r=>r.text()).catch(e=>e.name);while(serial===before)await pause(2);return {id:serial,c,pending};}
const a=await start();a.c.abort();const clientA=await a.pending;await pause(300);
const b=await start();const cancelB=await (await fetch(base+'/cancel?id='+b.id)).text();b.c.abort();await b.pending;await pause(300);
const c=await start();await c.pending;const cancelC=await (await fetch(base+'/cancel?id='+c.id)).text();
const result={date:'2026-11-05',environment:process.version,scope:'Local synthetic HTTP worker; worker intentionally ignores disconnect unless explicit cancel handler is called; not an AI provider test',cases:[{case:'abort_connection_only',client:clientA,worker:jobs.get(a.id).state},{case:'explicit_cancel_then_disconnect',cancel_reply:cancelB,worker:jobs.get(b.id).state},{case:'cancel_after_completion',cancel_reply:cancelC,worker:jobs.get(c.id).state}],events};
console.log(JSON.stringify(result,null,2));server.closeAllConnections();await new Promise(r=>server.close(r));
})();
. Save the fixture above as the named file and run it with Node.js; it uses a temporary loopback port and no external services. The 250-millisecond timer is a test setting, not a cancellation-latency measurement.
The experiment demonstrates a counterexample to a broad assumption: a client abort does not inherently stop every possible server implementation. It does not show how often this occurs in production, whether a specific AI provider propagates disconnections, or how any real generation would be charged.
Why is cancellation requested different from cancellation confirmed?
A service may acknowledge a cancellation request before the work reaches a final state, or report that work already completed. Treat the response according to the endpoint documentation. Do not convert every successful HTTP response into “cancelled.” Keep the requested action, acknowledgement and final state as separate facts when the interface distinguishes them.
For example, fal’s queue cancellation reference, checked November 5, 2026, distinguishes queued work from in-progress work. It describes removing a queued request and signaling a running app, with the app deciding how to stop. Its documented responses include cancellation-requested and already-completed outcomes. This illustrates why a generic “HTTP success means stopped” rule is insufficient; it is not a Token360 contract.
Proposed application states. Map these to the actual endpoint’s documented responses.
Consider a hypothetical race: the user presses Stop while a result is being finalized. The cancellation request and completion notification may reach your application in either order. Preserve both events, then derive the display from authoritative state rather than whichever callback happens to run last.
If the cancel request itself times out, retain “outcome unknown” until the documented status mechanism resolves it. Do not manufacture a confirmation, and do not immediately submit replacement generation merely because the cancel response was missing.
Which Token360 interfaces have documented cancellation behavior?
Check the specific Token360 API family rather than assuming every compatible endpoint has the same lifecycle operations. Async Chat documents job cancellation, while the Responses compatibility page lists lifecycle cancellation as unsupported. Media and other asynchronous interfaces have separate contracts. A capability in one family must not be projected onto another.
As of November 5, 2026, the Async Chat guide describes POST /v1/async/chat/completions/{id}/cancel. It says non-terminal jobs move to cancelled and already-terminal jobs return their current object. Job states are read through GET; Async Chat does not use a callback webhook. Use the actual job ID, not an unrelated application identifier.
The Response Protocol Compatibility page separately says that retrieving, cancelling or deleting Responses lifecycle objects is not currently supported. This is why changing the base URL in an SDK does not establish support for every SDK lifecycle method.
This article has not executed a live Token360 cancellation test. The public Async Chat state description establishes the documented application contract; it should not be expanded into a claim about exact upstream interruption timing, refunds or every video model. For those questions, inspect the relevant endpoint and account terms.
A useful integration review records three fields: the API family, its cancellation operation or lack of one, and the documented final-state mechanism. If any field is unknown, do not expose a UI promise that depends on it being known.
Does cancelling generation mean there is no charge?
Cancellation state and billing state answer different questions. A stopped or unwanted operation may already have consumed resources, and a missing final response may leave usage unobserved by the client. Verify the applicable billing rules and recorded usage separately. Do not translate “the user clicked Stop” into “nothing was generated” or “the request was free.”
For Token360 Async Chat, the documentation distinguishes a job ID from the platform-allocated request ID used for billing reconciliation. Retain both references for their respective purposes. General cost investigation belongs with Token360’s billing and usage guidance, rather than an estimate based on the last token visible on screen.
For a streaming interface, record whether final usage was actually received. If the stream ended early, a blank usage field means “not observed here,” not zero. For media, record whether the system accepted a job and what terminal state was later reported. Neither a stopped progress bar nor an absent download proves the absence of billable work.
Any refund or credit must be established by the applicable terms or an actual adjustment. This article does not report a cancelled-job cost experiment and makes no savings prediction.
How should an application present a cancellation race?
Use status text that reflects the strongest evidence currently available. Show “stopped receiving” for a local interruption, “cancellation requested” for an acknowledged request awaiting confirmation, and a confirmed terminal status when available. Keep uncertainty visible without forcing the user to understand internal implementation details or suggesting that a refund has already occurred.
Known event
Suggested user-facing message
Application follow-up
Local stream interrupted; remote state unknown
Stopped receiving. Checking request status.
Use a supported reconciliation path if one exists.
Cancel accepted but not final
Cancellation requested.
Wait for documented final state.
Job confirmed cancelled
Generation cancelled.
Record outcome; reconcile usage separately.
Completion won the race
Generation completed before cancellation.
Retain result and apply the product’s display policy.
No remote cancellation mechanism
Stopped waiting. Generation may continue.
Do not claim a remote stop.
These are proposed copy patterns, not Token360 console labels. Only show “checking” if your application actually has a supported status path. If it cannot obtain a final state, say that the outcome is unconfirmed and provide the appropriate support route.
Do not discard a late result merely to preserve the appearance that cancellation succeeded. Store the true operation outcome, then decide whether to present, retain or remove the asset according to the product’s separate policies. Cancellation should not silently erase the evidence needed to resolve the request.
What should a cancellation acceptance test cover?
Test the implementation at different lifecycle points and record both client behavior and the authoritative job outcome. Include late completion and an unreadable cancellation response, not only the happy path. A passing test must support the exact promise made by the UI; it cannot rely solely on the button becoming disabled.
Copy blog161-cancellation-checklist.csv:
Scenario,API family / model,Job ID,Request ID,Intent timestamp,Cancel response,Final state,Usage record,UI message,Unknowns
Before start,,,,,,,,,
During processing,,,,,,,,,
After completion,,,,,,,,,
Repeated click,,,,,,,,,
Cancel response lost,,,,,,,,,
Refresh while pending,,,,,,,,,
. It includes before-start, running, after-completion, duplicate-click and lost-response cases. These are proposed production checks; only the three local demonstrations above were executed for this article.
For each test, preserve the API family, model, request/job identifiers, stop-intent time, cancellation response and eventual state. Capture usage only from its appropriate record. Set a bounded reconciliation period and define what the application displays if that period ends without confirmation.
Test reconnecting or refreshing the UI as well. The page should reconstruct the recorded state instead of starting a second job or forgetting a pending cancellation. The goal is truthful lifecycle handling, not a promise that every upstream system supports immediate interruption.
Cancellation questions are easiest to answer when local intent, transport state, job state and billing state remain separate. These answers summarize that distinction without assuming a universal provider behavior. Use the current endpoint documentation for implementation, and treat the local demonstration as an educational example rather than a production compatibility result.
Does closing the tab cancel a generation?
Not necessarily. It can interrupt the client while separately managed work continues. Check how the application propagates the interruption and what the endpoint guarantees.
Is finish_reason: stop proof that the user cancelled?
No. Do not infer a user action from that field alone. Retain the explicit user event and interpret completion fields according to the selected protocol.
Can I copy a cancellation endpoint from another provider?
No. Use the operation documented for your platform and API family. Token360 Async Chat and Responses currently have different lifecycle support.
Does the local experiment prove Token360 continues after abort?
No. Its worker was deliberately programmed to ignore disconnects. No Token360 inference or cancellation request was submitted.
Does cancelling delete an already-generated asset?
Do not assume so. Stopping work, deleting a result and applying retention rules are separate operations unless the documented contract explicitly combines them.
Developer Guide
API
Build faster with one AI API.
Use Token360 to call video, image, audio, and text models with one key and one bill.