Published 13 Nov 2026

Designing the Status Endpoint for a Long-Running API

The status endpoint is a contract. Publish a small, stable state vocabulary instead of your internal states, report a failed job as a successful status call, and pick one status code convention and hold it.

Moving to asynchronous processing changes the shape of every response your clients deal with. The request that used to return the answer now returns a promise, and a second endpoint has to carry everything the first one used to.

That second endpoint is a contract, and it deserves the same design attention as the one that accepts the work. Most of them get none.

The accept call returns a 202 with enough information to find the job, either a Location header or a payload carrying the status URL. From there the client polls, and every decision about what comes back is a decision they will build against and depend on.

Do not expose your internal states

The most important call here, and the one most often skipped: the states you return to the client should be values you designed for the client, not the states your system happens to use internally.

Two reasons.

The first is that your internals are not the client’s business. Internal state and logging exist for you.

The second is the one that costs money later. Anything you return becomes something the client’s code branches on. Return your internal state values and you have published your worker’s state machine as API contract. Now renaming a stage, splitting one step into two, or reordering the pipeline is a breaking change. You end up versioning the API to accommodate a refactor that had nothing to do with the API.

A small, documented vocabulary solves both. Something like Queued, InProgress, Completed, Failed, with a written description of what each one means and what the client should do when they see it. Coarse and stable beats precise and fragile. The internal pipeline can change freely underneath as long as each internal stage maps to one of the published values.

Include an explicit terminal indicator alongside the state. Otherwise the client has to hardcode which of your state names mean “stop polling,” and you have handed them a second thing to break when the vocabulary grows.

Status codes on the status call

Pick one convention and hold it, because a client that sees two different codes for the same condition will hardcode whichever one it saw first.

While the work is running, two defensible options:

  • 200 with the state in the body. One source of truth. The client parses the body anyway to tell success from failure, so this gives them one thing to get right instead of two.
  • 202 while in progress, 200 once terminal. This is the Azure long-running operation convention, so enterprise integrators have likely met it before. It also gives Retry-After a natural home, which matters, because Retry-After is specified for 503, 429, and redirects, and looks out of place on a 200.

On completion, return a 200 and include a URL to fetch the result. Keeping the result behind its own URL keeps the status payload small for the many polls that return nothing useful, and gives you a place to apply different caching and authorization to the actual data.

Job failure is not a call failure

When the work fails, the status call still succeeded. It found the job, and it is telling you accurately what happened to it.

Return the terminal response with a Failed state and the error detail in the body. Do not return a 4xx or 5xx because the worker threw. A 5xx tells the client’s retry and monitoring stack that your API is broken, and they will back off or alert on a system that is working exactly as designed.

HTTP error codes on this endpoint belong to failures of the status call itself: the caller is not authorized, the operation does not exist, your service is genuinely down.

For the error detail, RFC 9457 Problem Details gives you a standard shape rather than one you invent. Worth using when the error needs to be machine-readable and not just human-readable.

Partial success deserves its own state. A job that finished with some items failed is not the same as a job that failed, and clients routinely handle one and not the other.