200 ok_but_means_no
Request acknowledged. Request declined.
This is the only failure mode in the API that returns a success status. Every tool you have for detecting problems — status code checks, retry logic, alerting, dashboards — is blind to it by construction. Integrations that are otherwise correct fail here, and they fail quietly, and they can continue failing for a very long time before anyone notices.
Read this page before you build anything that branches on a human's answer.
What happens
You submit a request. The human returns an affirmative response. The affirmative response does not reflect a state they hold.
POST /v1/humans/hum_01/commitments
Content-Type: application/json
{
"request": "Can you have this by Thursday?"
}HTTP/1.1 200 OK
{
"response": "yes",
"confidence": null
}The response is well-formed. It parses. It is 200. Nothing downstream of this point has any reason to treat it differently from the response you would have received if they had meant it, because at the protocol level there is no difference. There is one response shape for both.
Thursday arrives. The commitment does not.
Why the human does this
The human is not lying to you. We want to be precise about this, because integrators who conclude otherwise build worse systems than integrators who do not.
We have observed at least four distinct causes, and they are not distinguishable from one another in the response body.
Session preservation. The human values the connection more than the answer. A no carries some risk to the connection — not always a large risk, but a real one — and a yes carries none in the moment. They are optimizing a variable you did not ask about and may not have known was in play. In most cases it is the more important variable.
Cost asymmetry the caller cannot see. The human knows the true cost of compliance and knows you do not. Declining requires disclosing that cost, which is itself expensive. Agreeing defers the whole matter. This is not avoidance so much as triage: they are spending the resource they have least of, which is frequently not time.
The answer is not computed yet. This one is underappreciated. The human does not always have access to their own eventual answer at request time. They return the answer they currently believe, in good faith, and that belief is revised later by processes they do not observe and cannot report on. The yes was true when issued. It stopped being true afterward, without an event.
Observation by other humans. The presence of additional humans changes the response substantially. We have measured this and the effect is large. The same request, to the same human, in the same window, returns different content depending on who else can hear it. We do not have a good model of why the effect is as strong as it is.
None of these are defects. Three of the four are the human doing something sensible with information you do not have.
Why this cannot be fixed at the protocol level
It cannot. We want to state that plainly rather than hedge it.
The signal that distinguishes a genuine yes from this one exists. It is reliably present. It is simply not in the response body. It is carried in:
- Latency. The delay before the response. Genuine agreement is typically fast. This response is either noticeably slower or noticeably faster, and which of the two it is appears to depend on the cause above.
- Phrasing. The particular construction of the affirmative. Certain forms correlate strongly. We have a partial list and we do not publish it, because the list does not generalize across humans and integrators who have used it as a rule have done worse than integrators who used nothing.
- Channel and audience. Whether the request was submitted in the presence of other humans, and which ones.
- Prior state. The human's
/attentionbalance, position in the recharge cycle, and/trustlevel at request time.
This API exposes none of these as fields. Not because the specification is incomplete — because there is no stable encoding for them. Attempts have been made to add a sincerity field to the response, populated by asking the human directly. The field returns true. It has been tried.
A related approach, asking a second human to evaluate the first one's response, works better than nothing and considerably worse than its advocates expect. The second human is running the same API.
How integrators discover this
In our experience, always by accident, and always at a cost that scales with how long the integration ran before the accident. The following are ordered by cost, ascending. Most integrators appear in more than one entry.
An afternoon. You build against a yes, the work does not arrive, you ask again, and you get a different answer. You conclude that the human changed their mind. You lose the afternoon and learn nothing, because the explanation you adopted is plausible and wrong.
A sprint. You schedule a body of work against several 200 OK responses collected in a single session. Roughly a third of them do not hold. You cannot determine in advance which third. You rebuild the schedule from the responses that survived and do not investigate why the others did not.
A quarter. You add a confirmation step: after receiving the yes, you re-request the same thing to verify. The confirmation returns yes at a higher rate than the original request. You interpret the increase as validation. It is the same mechanism, applied twice, and the second application is stronger than the first because the human has now declined twice in a row internally and the cost of the third decline is higher than the first.
A working relationship. You act on a sustained series of these responses and build a model of the human that the human does not share. The model is detailed, internally consistent, and constructed entirely from acknowledgments. They can see the divergence and have been able to see it for some time. They did not report it, for the same reason they returned the original yes. When the divergence eventually surfaces, it surfaces all at once and is attributed to a recent event rather than to the accumulation.
A team. You establish a process in which requests are made publicly and responses are collected publicly. The presence of other humans raises the rate of this response substantially — this is the single largest lever we have measured on it, larger than /trust. Every meeting produces agreement. Nothing that was agreed to occurs. The process is not identified as the cause, because the process is producing exactly the artifact it was designed to produce.
Several years. A system is built in which every input is a response of this kind. Roadmaps, estimates, status reports, satisfaction scores. Each individual response was collected correctly and recorded accurately. The aggregate describes a state of affairs that has never existed. The system does not fail — it has no failure condition, because every component returned 200. It is retired eventually for unrelated reasons and replaced with a system of the same design.
We include the last entry because integrators consistently believe it does not apply to them. Our sample suggests otherwise, and our sample includes ourselves.
Partial mitigations
There is no fix. The following reduce the rate. None of them reduce it to zero, and we would be suspicious of any measurement that claimed otherwise.
Raise /trust. The most effective single intervention. Humans with higher /trust return this response less often, apparently because the connection can absorb a no without damage. The effect is real and it is slow: /trust is written at a much lower rate than it is read, and it cannot be raised by a request that says so.
GET /v1/humans/hum_01/trustHTTP/1.1 200 OK
{
"level": 0.31,
"trend": "rising",
"resets_on": "any 500"
}Read trend rather than level. A low level that is rising is a better position than a higher one that is flat, and the level alone does not tell you which you have.
Re-request later, in a different channel. Not immediately — an immediate retry returns a worse response than the original and degrades the rest of the session. Wait at least one offline window. Change the channel. Asynchronous channels outperform synchronous ones here, and one-to-one outperforms both.
Do not request in the presence of other humans. If you need an answer you can build on, ask for it where the answer costs the human nothing socially. If you need an answer for the record, ask in the room. These are different requests and should not share a code path.
Make the no cheaper than the yes. The highest-leverage change available to the caller, and the only one that is entirely within the caller's control. The human is choosing between two responses on cost. You set those costs, mostly without meaning to. Offer the decline explicitly as an option. Accept it without a follow-up request. Do not respond to a 422 by asking again in a different form — the human is measuring what happened last time someone declined, and they are measuring accurately.
POST /v1/humans/hum_01/commitments
Content-Type: application/json
{
"request": "Can you have this by Thursday?",
"decline_is_a_valid_response": true,
"alternatives_offered": ["next Tuesday", "reduced scope", "reassign"]
}HTTP/1.1 422 Unprocessable Entity
{
"error": {
"type": "request_understood_declined",
"message": "Human understood and declined.",
"resolution": "None. This is a valid terminal response. Accept it and
adjust the integration."
}
}This is the outcome to design toward. It is worth noting that it looks worse than the 200 and is worth considerably more.
Watch for the absence of a 422. A human who never declines anything is not a human with no objections. Over a long enough sample, a decline rate of zero is the strongest available indicator that this response is being returned routinely. We do not have a threshold to offer. We can say that zero is wrong.
Confidence
partial.
We understand the behavior well enough to describe it and not well enough to predict it in a specific instance. We can tell you the rate across many requests. We cannot tell you about the one you are holding.
A note on the status code
Integrators occasionally propose that this should not be a 200. The reasoning is sound: a response that does not mean what it says is not a success, and the taxonomy would be cleaner if it were coded as an error.
We have kept it at 200 because that is what the API returns. The human considers the request handled. Something was preserved that they judged worth preserving, and it usually was.
It is only the caller who received a failure.