HTTP 202 background jobs: accepted is not completed
Follow one background export from HTTP 202 to a finished file. Separate job status from a lost connection, and make retries and user messages tell the truth.
HTTP 202 means a request was accepted for processing, not that its work is complete. A background job needs a separate way to report progress and its result. Losing contact with that report makes the client uncertain; it does not prove that the job failed.
You ask a shop to repair a watch and receive a collection number. The number is not a repaired watch. If the shop does not answer your next call, you still do not know whether the repair is waiting, finished or unsuccessful.
The export button returns a successful HTTP response. The page immediately says “Export complete,” but the worker has not started. The application has confused two events: accepting a request and producing its result. Giving those events separate names is the first step towards fixing the message and the recovery path.
The request and the job#
RFC 9110 defines 202 Accepted as acceptance for processing that has not been completed. Processing might not ultimately happen. The response should describe the request’s current status and point to, or embed, a status monitor. HTTP does not later replace the original response with a new final status code.
That leaves application decisions to make. Where is accepted work stored? What starts it? How does a caller find its result? A status code is not a queue, a persistence mechanism or a recovery policy. Returning 202 before a job has been durably recorded can leave the caller with an acknowledgement and the system with nothing recoverable after a crash.
Consider an illustrative export API. It creates a job called E42, gives the client a status address and defines four job states: queued, running, succeeded and failed. In this proposed contract, acceptance is sent only after recording the job. These state names and the storage rule are our design, not additional guarantees hidden inside HTTP 202.
Follow one export#
The page needs to distinguish the last job state it confirmed from whether it can currently obtain updates. The following sequence keeps both visible.
| What happens | What the client knows | A truthful message |
|---|---|---|
| The API returns 202, job E42 and its queued status. | The request was accepted. This API explicitly reports that the job is queued. | Export requested. Waiting to start. |
| A status lookup reports running. | E42 was running when that status was observed. | Creating your export. |
| The next status lookup times out. | The last confirmed state was running. The current state is unknown to this client. | Cannot get an update. The export may still be running. |
| A later lookup reports succeeded and identifies a result. | E42 completed, and the response identifies the generated file. | Export ready. Open the file. |
| Alternatively, a lookup reports failed. | The job reports failure under the API’s contract. | The export failed. Show the useful error and the permitted next action. |
The two final rows are alternative outcomes, not consecutive steps in this example. A download can also fail after a job succeeds, or the resulting file can expire later. Those events do not turn the earlier computation into an unsuccessful one. The interface may need to distinguish “created successfully” from “currently available to download.”
Notice where “unknown” lives. Nothing in the timeout row tells the worker to change state. It is the browser that lacks a current observation. Keeping the last confirmed state, its observation time and update availability separate gives the user more information than replacing everything with a red “Failed” label.
Try it: report only what the client knows
Select an event in the illustrative export. A lost reply changes the client's knowledge; it does not change the job's state by itself.
When the reply disappears#
Change one condition: the original submission reaches the server and creates E42, but its response never reaches the browser. The client now has no collection number. Pressing “Try again” by sending a new submission may create E43, even though E42 is already working.
Design this case before choosing the button label. An idempotency key with an explicit reuse contract can let a repeated submission identify the same logical operation. Its scope, lifetime and behaviour when the payload changes must be defined. Merely adding a header without server-side handling does not prevent duplicates.
A status lookup is different: it asks what happened to E42 rather than requesting another export. Retry the lookup under a bounded policy and back off when the service cannot keep up. Whether updates use ordinary polling or a request held open for a later response, loss of contact still tells the client about its observation channel, not the job’s final outcome.
Make recovery part of the contract#
The export contract needs more than four labels. In this design, the status resource and the downloadable result both check the caller’s permission. Possessing a job identifier is not intended to grant access. The contract also says how long job records and files remain available; a record that can no longer be found must not be treated as proof that no work ever happened.
Define what cancellation promises before offering it. “Cancellation requested” and “Cancelled” are different observations. A worker may already have completed, or may need to reach a safe stopping point. Our four-state example deliberately leaves cancellation out; adding it would require its own transitions and outcomes rather than another optimistic button.
Define how a failed attempt relates to a retry too. Does the same logical job gain another attempt, or must an authorised caller create a new job? Which effects can already have happened? The appropriate answer depends on the work. Do not describe an arbitrary replay as recovery without considering those effects.
Make the message usable#
Correct states still need understandable messages. In the proposed interface, a connection warning says which update is unavailable and whether the user can retry the lookup. A job failure explains the next permitted action. Neither relies on colour alone or makes the user interpret an internal exception name.
WCAG’s status-message guidance explains how qualifying updates can be programmatically identified without moving focus. For a non-urgent progress message, an appropriate status region can make the update available to assistive technology. That is a markup decision to test in the actual interface, not proof of accessibility conformance.
Do not announce every unchanged polling response or interrupt the user for routine progress. Make meaningful changes available, retain a visible explanation and let the user keep working. A spinner cannot substitute for a result, and a permanently reassuring message cannot substitute for a missing observation.
IF YOU REMEMBER ONE THING
Report what was accepted, what the job has confirmed and what the client no longer knows. Those are three different facts; a reliable interface does not collapse them into one success or failure message.
Questions people also ask
4 QUESTIONSDoes HTTP 202 guarantee that the job will run?
No. RFC 9110 describes acceptance for processing, not completed processing or guaranteed execution. Durable storage, scheduling and recovery need an explicit application design. The status code alone supplies none of those guarantees.
Should a polling timeout mark the job as failed?
Not without other evidence. The client failed to obtain a current observation. Retain the last confirmed job state and show that updates are unavailable. The job may still be running or may already have finished.
Should a retry create another job?
A retry of a status lookup should inspect the existing job. Retrying the original submission is different and can create duplicate work unless the API has an explicit deduplication contract, such as a suitably scoped idempotency key.
Is unknown another server-side job state?
In this article's model, no. Queued, running, succeeded and failed describe the job. Unknown describes what the client currently knows. Keeping those separate avoids turning a connection problem into an invented job failure.