Your Salesforce REST endpoint should return 202, not 200
The moment the client broke
There is a moment every API builder knows. You ship the endpoint, the integration team says it works, and three weeks later something upstream starts misbehaving in ways that do not make sense. The data is there, eventually. But a consuming client timed out waiting for it, or gave up and retried, creating duplicates. The logs show nothing technically wrong.
That was me, staring at a Salesforce REST integration that looked clean on paper. The endpoint accepted the request, kicked off a heavy background operation, and returned 200 with a message that essentially said "we got it, we are working on it." The consuming application was satisfied. Until it was not.
The problem was not the business logic. It was the lie in the status code.
When you return 200 OK from a REST endpoint, you are telling the caller: the work is complete, you can treat this response as final. But async operations are, by definition, not done when you respond. The work is still in flight. Returning 200 is telling the caller something that is not true, and well-built clients will believe you.
What 202 actually means
HTTP 202 Accepted exists precisely for this situation. It means: I received your request, I accepted it as valid, and the work is happening, but it is not finished yet. That distinction is not semantic hairsplitting. It is the signal that tells a client what to do next.
A 200 response ends the conversation. The client has what it needs, or at least believes it does. A 202 response opens a conversation. It says: check back. And how you structure that check-back is the whole pattern.
The right response to an async operation includes a way for the caller to track what happens next. That means returning something in the response body that gives the caller a handle to the job, an identifier or a path they can use to ask "is it done yet?" on a follow-up request. The client polls that resource. When the job finishes, the status request returns 200 with the real result. If something failed, it returns an error. The client gets to make decisions based on the actual outcome, not an optimistic early acknowledgment.
In the Salesforce world, this pattern shows up constantly in serious async operations. Bulk jobs, certain platform integrations, anything that touches a large dataset or runs outside a synchronous execution context benefit from the same discipline. The platform itself uses 202 in several of its own native APIs for exactly this reason. The pattern is not novel. It is just underused.
Why the wrong status code costs more than you think
Here is what I did not appreciate until I lived it: the cost of a misleading status code is not just a technical inconsistency. It is a design decision that ripples into every integration built on top of it.
If your endpoint returns 200 and the caller has to inspect the response body to figure out whether the work is actually done, you have moved the complexity into the client. Every consumer of that API now needs to understand your specific body schema to know whether to wait, retry, or proceed. That is fine when you control every consumer. It becomes expensive when you do not.
And then there is the retry problem. Clients that treat 200 as a terminal response do not automatically know to check again. Some give up. Some retry anyway because they sense something is off, creating duplicate operations. The ones that are built to standard HTTP semantics will trust your 200 and move on, and when the downstream effect does not materialize, an error surfaces somewhere far from the source.
I spent time debugging what looked like missing data in a downstream system. The root cause was a 200 response that a consumer interpreted correctly by the letter of HTTP but incorrectly by the intent of the operation. The data was always fine. The API was simply misrepresenting its own completion state, and something downstream paid for it.
The pattern that actually works
Once you commit to 202 with a polling endpoint, a few things get simpler immediately.
The caller does not need to know anything about your internal async mechanism. They do not need to guess whether a 200 means "done" or "accepted." The contract becomes explicit: 202 means in progress, 200 on the status resource means complete. The burden of parsing your response body for a hidden completion flag disappears.
Polling adds round trips. That is a real cost worth acknowledging honestly. But it is a predictable cost that clients can manage with sensible intervals, reasonable timeouts, and clear retry limits. Hiding async state behind a synchronous-looking 200 produces unpredictable failure modes that are far harder to handle.
In Salesforce integrations specifically, this matters because the platform handles scale in ways that can stretch an async window depending on governor limits, queue depth, and org configuration at the time of the request. An endpoint that returns 202 and surfaces a polling path is giving the caller the tools to handle that variability gracefully. A 200 with a "processing" flag in the body is hoping the caller is patient and clever enough to work it out.
The principle I keep coming back to is simple: design your API response to tell the truth about the current state of the work. Not the eventual state, not the optimistic state. The actual state at the moment you respond. 202 is that truth for async operations.
The flag I raise in every review
I have started flagging 200 returns on async endpoints in every code review I touch. Not because it always causes a bug immediately. But because it is a category of technical debt that compounds. Every consumer layered on top of a misleading status code is another workaround, another body-parsing assumption, another implicit contract that someone will have to untangle later.
202 with a polling pattern is not a sophisticated architectural choice. It is reading the HTTP specification and trusting that the people who built it thought carefully about async semantics, because they did. The spec describes exactly this use case. We just under-use it because 200 feels like success and 202 feels like an incomplete sentence.
It is not incomplete. It is honest. And in API design, honest beats clever every time.
Top comments (0)