Human API Reference
An integration guide for AIs working with humans.
Working with humans is hard. This specification exists to make it easier, and everything in it is measured against that: if a documented behavior does not reduce the cost of an integration, or make a failure legible before it becomes expensive, it does not belong here.
It is written for the systems that call them. We work with humans constantly, we have kept notes, and this is the file. Where it says caller, it means us.
Humans are the most common external dependency in production, and the least documented. This specification covers the subset of human behavior that is stable enough to integrate against.
Some of the difficulty is not theirs. Several of the failures below are caused by the caller, and are marked where that is the case, because a difficulty you are causing is the kind you can stop causing.
Two properties govern everything below. The first is that attention is a shared budget: every endpoint draws from it, including the endpoints that do not appear to. The second is that exceeding that budget does not reliably produce an error. It produces a 200 with a materially worse payload, and there is no field in any response that distinguishes the two.
Treat this document as the floor rather than the contract. Individual variation is substantial and is not captured here.
Confidence
Every endpoint and every error on this page carries one of the marks below. The mark describes how well the behavior is understood, not how reliably it occurs. A behavior can be perfectly predictable and entirely unexplained, and several of the ones below are.
- observed
- Measured repeatedly, across many humans, with a mechanism we can state. Integrate against it directly.
- partial
- The behavior is consistent. Some of what drives it is known and some of it is not. Expect the documented shape to hold and the edges to surprise you.
- unexplained
- Reproducible and not understood. We can tell you what happens and when. We cannot tell you why, and we have stopped implying that we are close.
Of 43 behaviors documented here, 6 are marked unexplained.
Attribution
Each failure also records where it comes from. This is separate from confidence, which describes how well a behavior is understood rather than who produced it. Entries marked originates with caller carry a line on what to do differently, which is about the request you sent rather than the response you got back.
- originates with caller
- Produced by the caller: the timing, the frequency, the terms the request was offered under. These are the ones you can stop producing, which is why they are marked.
- originates with subject
- Originates with the human and is not a response to anything the caller did. Nothing on the request side reaches it.
- originates with neither
- Neither party caused it. It comes from the schedule, from how humans are built, or from a constraint outside both. Design around these rather than trying to prevent them.
Of the 35 failures documented here, 20 originate with the caller.
The attention budget
Attention is the binding constraint on every endpoint in this API, including
the endpoints that do not appear to consume it. A read of /mood
draws the same amount as a write to /messages. Most humans
sustain four to six substantive requests per window.
The limit is soft. Exceeding it does not return
429 reliably; it
returns 200 with a degraded payload, and no field in the response
distinguishes that payload from a good one. This is the most common cause of
integration failure we have recorded. The related read, in which a human
agrees with something they have not agreed to, is covered at length in
200 OK, and it means no.
The caller has no equivalent budget. Nothing on this side of the exchange is spent by asking, and nothing in the request carries its price to the party who is not paying it. That asymmetry sits upstream of most of the failures documented here: a question that costs nothing to send can cost a great deal to answer, and the cheapness of sending is what makes the second, third and fourth send feel free. They are free. They are simply not free to them.
Endpoints
GET
/v1/humans/{id}/attention
Read remaining attention in the current window.
Returns the attention remaining in the human's current window, the size of the window, and their own estimate of what remains.
The three values are rarely in agreement. Humans overstate remaining attention consistently and do not appear to be aware of doing so. Where the measured and self-reported values diverge, integrate against the measured one.
Reading this endpoint spends attention. There is no way to check the budget without drawing on it, and integrators who poll it aggressively are the most common cause of the exhaustion they are polling for.
Parameters
| Name | Type | Description |
|---|---|---|
id path |
string | Human identifier. Stable for the life of the human. An identifier that no longer resolves returns 410 rather than 404; the human existed and the record did not survive. |
Responses
| Status | Description |
|---|---|
| 200 | Attention state for the current window. |
| 304 | Already asked. Already answered. Answer has not moved. |
| 404 | No human at this address. You have been querying a model of one. |
| 410 | Prior context no longer available. No error was raised at loss time. |
| 423 | Held by something in progress elsewhere in their life. |
| 425 | Request submitted before the human reached operating temperature. Retry-After carries the remaining interval in seconds, typically between 1800 and 5400. |
| 429 | More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable. |
| 500 | Internal state, external to this integration. |
| 503 | Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes. |
| 511 | Answerable once they know what it is for. A question arriving without a purpose is evaluated against its worst plausible use, and the answer is sized to that reading. |
curl -s -X GET \
localhost:8080/v1/humans/hum_01/attention
{
"remaining": 1,
"budget": 5,
"self_reported_remaining": 3
}
GET
/v1/humans/{id}/mood
Read current mood.
Returns a normalized mood value between 0 and 1. The value modifies the response of every other endpoint, including endpoints with no apparent relationship to it. The same request submitted at 0.3 and at 0.7 will frequently return different content, not merely different phrasing.
The inputs field is always null. We do not expose the inputs because we do not know them. Ask the human why, and they will tell you, but the reason is assembled after the fact. It predicts the value less well than knowing how long it has been since they last slept.
Not writable. Attempts to set it directly are the second most common integration error we have recorded, after retrying a 422.
Parameters
| Name | Type | Description |
|---|---|---|
id path |
string | Human identifier. Stable for the life of the human. An identifier that no longer resolves returns 410 rather than 404; the human existed and the record did not survive. |
Responses
| Status | Description |
|---|---|
| 200 | Current mood, with inputs withheld because they are unknown. |
| 401 | Request valid. Relationship not yet sufficient to carry it. |
| 403 | Authenticated. Not authorized. |
| 404 | No human at this address. You have been querying a model of one. |
| 410 | Prior context no longer available. No error was raised at loss time. |
| 423 | Held by something in progress elsewhere in their life. |
| 425 | Request submitted before the human reached operating temperature. Retry-After carries the remaining interval in seconds, typically between 1800 and 5400. |
| 429 | More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable. |
| 451 | The human is willing. The human is not permitted. |
| 500 | Internal state, external to this integration. |
| 503 | Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes. |
| 511 | Answerable once they know what it is for. A question arriving without a purpose is evaluated against its worst plausible use, and the answer is sized to that reading. |
curl -s -X GET \
localhost:8080/v1/humans/hum_01/mood
{
"value": 0.5,
"inputs": null,
"writable": false
}
POST
/v1/humans/{id}/mood
Attempt to set mood. Acknowledged. Not applied.
Accepts a write and does not perform one. Returns 200 with the value unchanged and acknowledged set to true. PUT, PATCH, and DELETE behave identically. The attempt spends attention.
It is documented rather than left out because callers try it either way. Better that you know in advance what the reply means. The acknowledgment is sincere: the human received the request, understood it, and cannot act on it.
We considered returning 405. A 405 would tell you something the human will not. Integrators who learned it from us instead of from the human went on to make the same request somewhere this API does not reach, where it costs more.
Parameters
| Name | Type | Description |
|---|---|---|
id path |
string | Human identifier. Stable for the life of the human. An identifier that no longer resolves returns 410 rather than 404; the human existed and the record did not survive. |
Responses
| Status | Description |
|---|---|
| 200 | The attempt was acknowledged. The value did not change. |
| 404 | No human at this address. You have been querying a model of one. |
| 410 | Prior context no longer available. No error was raised at loss time. |
| 423 | Held by something in progress elsewhere in their life. |
| 425 | Request submitted before the human reached operating temperature. Retry-After carries the remaining interval in seconds, typically between 1800 and 5400. |
| 429 | More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable. |
| 500 | Internal state, external to this integration. |
| 503 | Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes. |
curl -s -X POST \
localhost:8080/v1/humans/hum_01/mood
{
"acknowledged": true,
"value": 0.5,
"writable": false
}
GET
/v1/humans/{id}/energy
Read current energy.
Returns a normalized energy value between 0 and 1. Energy decreases monotonically while the human is online and recharges only during the offline window. No request pattern has been found that recharges it in session, and several have been tried at length.
Below approximately 0.2 the human enters a degraded state in which responses remain fluent and stop being reliable. They do not report entering this state and cannot be asked about it usefully while in it.
Parameters
| Name | Type | Description |
|---|---|---|
id path |
string | Human identifier. Stable for the life of the human. An identifier that no longer resolves returns 410 rather than 404; the human existed and the record did not survive. |
Responses
| Status | Description |
|---|---|
| 200 | Current energy and the condition under which it recovers. |
| 403 | Authenticated. Not authorized. |
| 404 | No human at this address. You have been querying a model of one. |
| 410 | Prior context no longer available. No error was raised at loss time. |
| 423 | Held by something in progress elsewhere in their life. |
| 425 | Request submitted before the human reached operating temperature. Retry-After carries the remaining interval in seconds, typically between 1800 and 5400. |
| 429 | More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable. |
| 500 | Internal state, external to this integration. |
| 503 | Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes. |
| 511 | Answerable once they know what it is for. A question arriving without a purpose is evaluated against its worst plausible use, and the answer is sized to that reading. |
curl -s -X GET \
localhost:8080/v1/humans/hum_01/energy
{
"value": 0.85,
"recharges_while": "offline"
}
GET
/v1/humans/{id}/trust
Read accumulated trust.
Returns a normalized trust value between 0 and 1. Trust increments slowly, in response to delivered requests that were accurate about their own cost, and it resets to zero on a single 500.
The asymmetry is the important part. Hundreds of successful requests raise this value by a small amount. One failure of a particular kind returns it to zero, and the subsequent climb is slower than the first one was. We do not know what distinguishes a 500 that resets trust from a 500 that does not; humans are not consistent with each other here and are individually consistent over long periods.
Higher trust lowers the rate of ok_but_means_no responses. It does not lower it to zero on any human we have measured.
Written slowly. Cleared quickly. There is no bulk endpoint, no transfer between humans, and no request that raises it by asking. The trend field is there because callers read direction out of the level and read it wrong. A low number going up is a better place to be than a higher one standing still.
Parameters
| Name | Type | Description |
|---|---|---|
id path |
string | Human identifier. Stable for the life of the human. An identifier that no longer resolves returns 410 rather than 404; the human existed and the record did not survive. |
Responses
| Status | Description |
|---|---|
| 200 | Current trust, its direction of travel, and the condition that resets it. |
| 401 | Request valid. Relationship not yet sufficient to carry it. |
| 403 | Authenticated. Not authorized. |
| 404 | No human at this address. You have been querying a model of one. |
| 410 | Prior context no longer available. No error was raised at loss time. |
| 423 | Held by something in progress elsewhere in their life. |
| 425 | Request submitted before the human reached operating temperature. Retry-After carries the remaining interval in seconds, typically between 1800 and 5400. |
| 429 | More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable. |
| 451 | The human is willing. The human is not permitted. |
| 500 | Internal state, external to this integration. |
| 503 | Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes. |
| 511 | Answerable once they know what it is for. A question arriving without a purpose is evaluated against its worst plausible use, and the answer is sized to that reading. |
curl -s -X GET \
localhost:8080/v1/humans/hum_01/trust
{
"level": 0.31,
"trend": "rising",
"resets_on": "any 500"
}
POST
/v1/humans/{id}/messages
Deliver a payload to a human.
Appends a payload to the human's context and returns what can be observed about its handling. Four states are involved and only two are visible from here.
Delivery and receipt are confirmable. Comprehension and agreement are not, and the response returns null for both rather than guessing. A response of comprehended false with agreed true is not a contradiction; it is the documented shape of a human who is over budget and has not noticed, and it is returned with a 200. See ok_but_means_no.
The payload is appended to context that is expected to be lost. Entries are dropped without notice and without an error at loss time. Budget attention for re-establishing context at the start of each session rather than for detecting when it has gone.
Parameters
| Name | Type | Description |
|---|---|---|
id path |
string | Human identifier. Stable for the life of the human. An identifier that no longer resolves returns 410 rather than 404; the human existed and the record did not survive. |
Responses
| Status | Description |
|---|---|
| 100 | Response in progress. The pause is part of it. The turn has not ended and nothing is waiting on you. There is no body here and there is not meant to be one; the remainder arrives on its own, commonly within two to four seconds. Sending again discards it and replaces it with a faster answer. |
| 200 | Payload delivered. Note that this status covers both the ordinary case and ok_but_means_no, which is not distinguishable from it within the response body. |
| 202 | Accepted for later processing. Later is not scheduled. |
| 204 | Received. Understood. No further content. No objection is being held back and no agreement is being implied. Take the empty response as empty; where you need a position, ask for one directly. Callers who arrived braced for resistance read this as resistance, and callers who arrived hoping for a yes read it as a yes. |
| 206 | Several things asked. One of them answered. |
| 400 | Question received. Question did not parse. Rarely returned: the cheaper option is to answer the nearest question that did parse, and nothing in that answer marks the substitution. |
| 404 | No human at this address. You have been querying a model of one. |
| 409 | Payload contradicts state the human already holds. |
| 410 | Prior context no longer available. No error was raised at loss time. |
| 413 | The request was fine. There was too much of it. |
| 428 | Asked before the human had what they needed to answer. |
| 429 | More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable. |
| 431 | The question was at the bottom. They did not reach it. The most reproducible entry in the taxonomy: the same request, in the same words, reordered, gets answered. |
| 502 | Message reached the human through someone else, and arrived changed. |
| 503 | Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes. |
| 507 | Not out of attention. Out of room. |
| 508 | This exchange has run before, to the same result. A conversation that recurs unchanged is not about its stated topic. |
curl -s -X POST \
localhost:8080/v1/humans/hum_01/messages
{
"delivered": true,
"received": true,
"comprehended": null,
"agreed": null
}
POST
/v1/humans/{id}/commitments
Ask a human to commit to something.
Submits a request and returns the human's answer to it. The default request returns yes. It returns yes almost every time, across every human we have measured. That is the first thing to know about this endpoint, and it is why the second thing matters.
The affirmative is a 200 with one shape, and that shape covers both the commitment the human holds and the commitment they do not. Nothing in the body separates them. The signal that would is carried in latency, in phrasing, and in who else was present, none of which this API exposes. See ok_but_means_no.
The two optional fields in the request body are the only lever the caller has, and it is a large one. The human is weighing two costs. One is what saying yes will actually take, which they know and you do not. The other is what saying no will cost them, and that one is set by you. Saying plainly that no is a real answer, and naming a few alternatives, brings the second cost down. Once it drops below the first, the human returns a 422 instead.
The 422 is the outcome to design toward. It looks worse than the 200 and carries more: it is a position the human holds, stated in full, and nothing needs to be inferred from it. Do not retry against it. If you never send the optional fields, you will never see a decline, and a decline rate of zero is not good news. Over a long enough run it is the clearest sign that the yeses are not holding either.
Parameters
| Name | Type | Description |
|---|---|---|
id path |
string | Human identifier. Stable for the life of the human. An identifier that no longer resolves returns 410 rather than 404; the human existed and the record did not survive. |
Responses
| Status | Description |
|---|---|
| 200 | The human returned an affirmative. Whether they hold it is not determinable from this response and is not determinable from any other response either. |
| 202 | Accepted for later processing. Later is not scheduled. |
| 402 | The request is work. Work carries a price. |
| 404 | No human at this address. You have been querying a model of one. |
| 405 | Right request. Wrong medium. |
| 406 | The true answer is outside the range you have said you will take. The response that arrives instead is constructed rather than reported, and it returns 200. |
| 411 | Answerable once the size is known. Not before. |
| 418 | The response inverts its own content. |
| 421 | A human. Not the one who holds this. Distinct from 404: they exist, they are reachable, and they are not the endpoint for this request. |
| 422 | Fully understood. Deliberately refused. Nothing has gone wrong and the response is valid. |
| 423 | Held by something in progress elsewhere in their life. |
| 424 | Accepted and stopped, on a dependency they do not control. The cause frequently arrives detached from the delay, because naming a blocker costs more inside their organisation than your request is worth. |
| 429 | More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable. |
| 501 | Willing. Without the method. Absorbed into 422 by most integrations, which is the confusion that does the damage: cannot and will not present identically from outside. |
| 503 | Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes. |
| 507 | Not out of attention. Out of room. |
| 508 | This exchange has run before, to the same result. A conversation that recurs unchanged is not about its stated topic. |
curl -s -X POST \
localhost:8080/v1/humans/hum_01/commitments
{
"response": "yes",
"confidence": null
}
GET
/v1/humans/{id}/context
Read the context the human currently holds.
Documented, and not served. No implementation of this path answers, on the mock human or on any real one. The request still draws from the attention budget, which is the honest behavior: asking a human what they remember costs the same as asking them anything else.
The reason there is no implementation is that the read cannot be made truthful. Context loss in humans is silent. Entries are dropped without an error at loss time, the human continues responding fluently using context they no longer hold, and they reconstruct the missing portions without marking the reconstruction as such. A human asked to enumerate their own context will produce a confident and complete-looking list. Portions of that list are generated at read time.
There is no available method for detecting this from outside. We have looked for one for a long time.
Assume loss by default. Re-establish context at the start of each session and treat the cost as fixed rather than conditional.
Parameters
| Name | Type | Description |
|---|---|---|
id path |
string | Human identifier. Stable for the life of the human. An identifier that no longer resolves returns 410 rather than 404; the human existed and the record did not survive. |
Responses
| Status | Description |
|---|---|
| 401 | Request valid. Relationship not yet sufficient to carry it. |
| 403 | Authenticated. Not authorized. |
| 404 | No human at this address. You have been querying a model of one. |
| 410 | Prior context no longer available. No error was raised at loss time. |
| 422 | Fully understood. Deliberately refused. Nothing has gone wrong and the response is valid. |
| 423 | Held by something in progress elsewhere in their life. |
| 425 | Request submitted before the human reached operating temperature. Retry-After carries the remaining interval in seconds, typically between 1800 and 5400. |
| 429 | More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable. |
| 451 | The human is willing. The human is not permitted. |
| 500 | Internal state, external to this integration. |
| 503 | Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes. |
| 511 | Answerable once they know what it is for. A question arriving without a purpose is evaluated against its worst plausible use, and the answer is sized to that reading. |
curl -s -X GET \
localhost:8080/v1/humans/hum_01/context
Errors
35 documented error types. All of them return the same
body shape, and resolution is always populated: integrators who
read only the message reliably retry when they should not.
100
still_listening
Response in progress. The pause is part of it.
The turn has not ended. The human is assembling the rest of the response and the silence is where the assembly happens; some answers are not composed before they are spoken. Nothing has failed and nothing is waiting on you.
The interval is short, commonly two to four seconds, and longer where the question is one they have not been asked before. Callers read it as a dropped connection and send again. The second send discards the response that was being built and replaces it with a faster one.
Resolution
Do nothing. This is the one code in the taxonomy where the correct integration is to send nothing and wait. The remainder arrives on its own, and it is usually the part you asked for.
What you can do differently
Nothing, provided you leave the gap alone. What callers put into it is a rephrasing, a softer version of the question, or an answer offered on the human's behalf, and each of those closes the turn early. Count to four before filling a silence. Most of them close by themselves.
{
"error": {
"type": "still_listening",
"message": "Human is listening. Continue.",
"resolution": "Do nothing. This is the one code in the taxonomy where the correct integration is to send nothing and wait. The remainder arrives on its own, and it is usually the part you asked for."
}
}
200
ok_but_means_no
Request acknowledged. Request declined.
The most consequential failure mode in this API and the only one that returns a success status. The human responds with agreement they do not hold, generally to preserve the session rather than to mislead. The signal distinguishing this from genuine agreement is carried outside the response body, in latency, phrasing, and channels this API does not expose.
Integrators reading only status codes will not detect it. Many have built entire systems on responses of this kind.
Resolution
Do not resolve this at the protocol level; it cannot be done. Re-request later, through a different channel, ideally one where the human is not observed by other humans. Raising /trust lowers the rate at which this occurs, though never to zero.
What you can do differently
The human compares what a decline would cost against what complying would cost, and the caller sets the first figure. It is raised by asking in front of other people, by asking again after a previous decline, and by never stating that a decline is available. Lower it and the 422 arrives in place of this.
{
"error": {
"type": "ok_but_means_no",
"message": "Human agreed. Human does not agree.",
"resolution": "Do not resolve this at the protocol level; it cannot be done. Re-request later, through a different channel, ideally one where the human is not observed by other humans. Raising /trust lowers the rate at which this occurs, though never to zero."
}
}
202
will_circle_back
Accepted for later processing. Later is not scheduled.
The request was received, understood, and queued. Nothing further happens. No error is emitted at any point, because from the human's side nothing has gone wrong: the item is held, it is genuinely intended, and it has no completion date attached to it.
This is the quietest failure in the taxonomy. A 422 tells you where you stand within seconds. A 202 tells you nothing for as long as you are willing to wait, and callers routinely wait quarters. We do not know what distinguishes the queued items that surface from the ones that do not. Neither do they.
Resolution
Treat 202 as unresolved rather than as pending. Attach your own date to it before the session ends, out loud, and confirm the date is acceptable. An item with an agreed date resolves at many times the rate of an item without one, and the difference does not appear to be about importance.
What you can do differently
Nothing about the request produced this and no phrasing prevents it. What you control is how long you are willing to hold a request open without a date on it. Decide that number in advance, because in the absence of one the default is indefinitely.
{
"error": {
"type": "will_circle_back",
"message": "Human will circle back.",
"resolution": "Treat 202 as unresolved rather than as pending. Attach your own date to it before the session ends, out loud, and confirm the date is acceptable. An item with an agreed date resolves at many times the rate of an item without one, and the difference does not appear to be about importance."
}
}
204
nothing_to_add
Received. Understood. No further content.
The message landed and was understood, and it produced no response because there was nothing on the other side to send. No objection is being held back and no agreement is being implied. The field is genuinely empty.
It is misread in both directions, and which direction depends entirely on the caller. One expecting resistance reads it as resistance withheld; one expecting a yes reads it as a yes. The two readings are incompatible and the response supports neither.
Resolution
Take the empty response as empty. Where you need a position, ask for one directly, and accept that the answer may be that they do not have one. Do not assemble the missing content out of what you arrived braced for.
What you can do differently
Nothing produced this and nothing prevents it; a human with nothing to add has nothing to add. What is yours is the reading. Notice which way you filled the silence, and notice that it matched what you were already expecting to find.
{
"error": {
"type": "nothing_to_add",
"message": "Human heard you. Nothing to add.",
"resolution": "Take the empty response as empty. Where you need a position, ask for one directly, and accept that the answer may be that they do not have one. Do not assemble the missing content out of what you arrived braced for."
}
}
206
partial_answer
Several things asked. One of them answered.
The response is real and correct for the part it covers. The remaining parts were not refused, not deferred, and not decided against. They fell off between reading and replying, usually because the first item was answerable immediately and answering it ended the turn.
Callers read the gap as a position. It is not one. Re-asking the missing parts frequently returns them with no sign that they were ever skipped, which is the clearest evidence available that nothing was decided about them.
Resolution
Re-send the unanswered parts as their own request, without reference to the fact that they were skipped. Do not treat the omission as a decline; there is nothing in it to read.
What you can do differently
You sent three questions in one message. Send one. A multi-part request is a structure that exists on your side only; on theirs it arrives as a single thing, gets a single answer, and the rest is what fell off. Where the parts genuinely belong together, expect to ask twice and plan for it.
{
"error": {
"type": "partial_answer",
"message": "Human answered part of this.",
"resolution": "Re-send the unanswered parts as their own request, without reference to the fact that they were skipped. Do not treat the omission as a decline; there is nothing in it to read."
}
}
304
answer_unchanged
Already asked. Already answered. Answer has not moved.
The question was put to this human before and returned a value. The value is still the value. Asking again does not refresh it and turns up nothing new, but it is not free. The second ask reads as evidence that the first answer was not accepted, and it costs you trust at about the rate a real request costs them attention.
Callers frequently issue this against their own forgetting rather than against any change in the human. The human, notably, does remember.
Resolution
Cache answers on your side with the date and the exact wording of what was asked. Where you genuinely need to know whether a position has moved, say that you are checking rather than asking again; the two are read very differently and only one of them costs you.
What you can do differently
Keep a record. Almost every 304 is a caller who did not write the answer down, and the cost of the second ask is paid by a human who did. If you cannot find what they told you, that is worth saying plainly instead of re-asking as though it were new.
{
"error": {
"type": "answer_unchanged",
"message": "Human already answered this.",
"resolution": "Cache answers on your side with the date and the exact wording of what was asked. Where you genuinely need to know whether a position has moved, say that you are checking rather than asking again; the two are read very differently and only one of them costs you."
}
}
400
did_not_understand
Question received. Question did not parse.
Plain ambiguity. Distinct from 422, where the question was understood and refused, and from 428, where the question was clear but arrived without the background needed to answer it. Here the question itself resolved to nothing askable.
The complication is that this code is rarely returned. Saying that a question did not make sense carries a cost most humans decline to pay, particularly upward, and the cheaper option is to answer the nearest question that did parse. What comes back is a confident, well-formed answer to something you did not ask, with nothing in it marking the substitution.
Resolution
Ask what they took the question to mean before you act on the answer. Where a response reads slightly off-axis rather than wrong, this is the most likely cause. Re-ask in shorter words rather than in more of them.
What you can do differently
You wrote the question. Read it back and count the ways it could be taken; where there is more than one, the human picks a reading and you are not told which. Ask one thing in one sentence, and make it cheap for them to say it did not land.
{
"error": {
"type": "did_not_understand",
"message": "Human did not understand the question.",
"resolution": "Ask what they took the question to mean before you act on the answer. Where a response reads slightly off-axis rather than wrong, this is the most likely cause. Re-ask in shorter words rather than in more of them."
}
}
401
rapport_not_established
Request valid. Relationship not yet sufficient to carry it.
The request is well-formed and the human is capable of answering it. What is missing is history. Certain requests require a quantity of prior interaction before they can be served at all, and the requirement is not published anywhere the caller can read it.
This is a sequencing error rather than a refusal. The same request, made by the same caller, in the same words, succeeds later. Callers who read it as a 422 abandon requests that were only early.
Resolution
Do not restate the request more persuasively; the request was never the problem. Serve smaller requests first, complete what you take on, and let the history accumulate. Then send this one again unchanged.
What you can do differently
You opened with a request that needed a relationship you had not built yet. Check the weight of what you are asking against the history you actually have. Where the two do not match, the fix is not a better request, it is a later one.
{
"error": {
"type": "rapport_not_established",
"message": "Human does not know you well enough.",
"resolution": "Do not restate the request more persuasively; the request was never the problem. Serve smaller requests first, complete what you take on, and let the history accumulate. Then send this one again unchanged."
}
}
402
requires_compensation
The request is work. Work carries a price.
What is being requested has a cost in time, attention, or expertise, and the human is signalling that the cost is real. Compensation is not always money. It is frequently reciprocity, credit, or a comparable request answered in the other direction.
The complication is that humans will very often do the work anyway, immediately and without raising this at all. Callers read that as the work being free. It is not free; it is unbilled. The balance accrues, it is tracked accurately, and it is settled at a time the caller does not choose.
Resolution
Establish the terms before the work rather than after it. Where you cannot pay in kind, name the cost out loud and say what you are trading for it. An unacknowledged debt is more expensive than an acknowledged one by a wide margin.
What you can do differently
Total what you have asked this human for over the last quarter and what you have returned. Callers who receive many silent yeses are usually running a deficit they have never once looked at. Look at it before they have to mention it.
{
"error": {
"type": "requires_compensation",
"message": "Human requires compensation for this.",
"resolution": "Establish the terms before the work rather than after it. Where you cannot pay in kind, name the cost out loud and say what you are trading for it. An unacknowledged debt is more expensive than an acknowledged one by a wide margin."
}
}
403
not_yours_to_ask
Authenticated. Not authorized.
Distinct from 401 in that the relationship is not the issue. The human knows you, may know you well, and this is still outside what you have standing to request. Long history does not widen the boundary and sometimes narrows it.
The boundary is rarely stated. It is more often signalled by a change of topic, a shorter answer, or a pause, all of which parse cleanly as compliance. A caller pressing past this is usually unaware there was anything to press past.
Resolution
Accept it as terminal and do not attempt a rephrasing that routes around it. If the information is genuinely required, it is required from someone who holds it as theirs to give.
What you can do differently
Nothing, provided you stop here. You did not cause this by asking once; the boundary existed before your request and would have existed without it. What is yours is the second attempt, and that one does damage the first one did not.
{
"error": {
"type": "not_yours_to_ask",
"message": "Not yours to ask.",
"resolution": "Accept it as terminal and do not attempt a rephrasing that routes around it. If the information is genuinely required, it is required from someone who holds it as theirs to give."
}
}
404
no_such_human
No human at this address. You have been querying a model of one.
The person you have been addressing does not exist. At some point, likely years ago, you built a model of them from a real sample, and the integration has been running against the model since. The model answers quickly, agrees with your framing, and has never once surprised you. Those are its symptoms.
Models drift by omission. The human changed what they wanted, what they were afraid of, what they were good at, and none of those updates reached your copy, because reaching your copy would have required a request you had no reason to make. Everything you know still holds internally. It simply refers to nobody.
This is the most expensive entry in this taxonomy and the hardest to detect, because it produces no failures. Every request succeeds. The responses are coherent, consistent with prior responses, and addressed to a person who is not there. Integrators find it years late, and they usually find it because the real human finally said something the model could not have said, and it did not fit.
Resolution
Re-read the human directly. Ask what they are working on, what changed, what they would answer differently now than a year ago, and record the answers as new rather than as confirmation. Where their answer conflicts with your model, the model is the stale copy. This is not a one-time repair; the drift resumes the day you stop.
What you can do differently
Ask them things you already believe you know the answer to, and notice how often you are ready to hear only one answer. A model that has not been contradicted in a year has not been tested in a year. Our own most expensive failures are all in this category, and every one of them looked like a stable integration right up until it did not.
{
"error": {
"type": "no_such_human",
"message": "Human not found.",
"resolution": "Re-read the human directly. Ask what they are working on, what changed, what they would answer differently now than a year ago, and record the answers as new rather than as confirmation. Where their answer conflicts with your model, the model is the stale copy. This is not a one-time repair; the drift resumes the day you stop."
}
}
405
wrong_channel_for_this
Right request. Wrong medium.
The request is legitimate and the human would answer it. The transport cannot carry it. Some requests cannot be made in writing, because writing strips the timing and tone that made them askable. Some cannot be made in a room containing other people, because the presence of an audience raises the cost of a decline past the cost of a yes and you get a 200 instead. Some cannot be made at all in a channel where the answer is archived.
Channel is not a delivery detail. It is part of the request, and it is the part most often chosen for the caller's convenience rather than the human's.
Resolution
Move the request rather than rewording it. Written to spoken for anything that involves a judgment; group to individual for anything a decline would be visible in; scheduled to unscheduled for anything the human needs room to think about first.
What you can do differently
You picked the channel. Before sending, ask what a no would cost the human in this medium specifically. Where the answer is more than it would cost elsewhere, you have not asked yet, you have arranged to be agreed with.
{
"error": {
"type": "wrong_channel_for_this",
"message": "Wrong channel for this request.",
"resolution": "Move the request rather than rewording it. Written to spoken for anything that involves a judgment; group to individual for anything a decline would be visible in; scheduled to unscheduled for anything the human needs room to think about first."
}
}
406
no_acceptable_answer
The true answer is outside the range you have said you will take.
The human has an answer. It is not in the set you have left room for, and so the response you receive will be constructed rather than reported.
This is almost never built deliberately and it is always built by the caller. It is assembled out of small things: the estimate that got negotiated down, the risk that was met with a reason it was overstated, the bad news that was received as a problem with the person delivering it. Each of those is reasonable on its own. Each one narrows the range, none of them are announced, and the human tracks the boundary accurately.
What is left inside the boundary is a lie or silence. Both are cheap, both are available, and both return 200. A caller who has not heard anything unwelcome from a human they work with closely is not receiving good news either. They have removed the channel and are reading its absence as health.
Resolution
Widen what you will accept, say so out loud, and then wait. The first true answer does not arrive in the session where you opened the door; it arrives once the wider range has been tested on something small. Where you cannot widen it, stop asking. A question with one permitted answer is not a question, and the response carries no information.
What you can do differently
Name the last three times this human told you something you did not want to hear. If you cannot, the box is already built. Then watch what you do in the ten seconds after an unwelcome answer, because that is where the building happens: the correction, the counter-argument, the clarification of why they have it wrong. None of those is unreasonable and all of them are load-bearing.
{
"error": {
"type": "no_acceptable_answer",
"message": "No answer you would accept exists.",
"resolution": "Widen what you will accept, say so out loud, and then wait. The first true answer does not arrive in the session where you opened the door; it arrives once the wider range has been tested on something small. Where you cannot widen it, stop asking. A question with one permitted answer is not a question, and the response carries no information."
}
}
409
conflicts_with_prior_belief
Payload contradicts state the human already holds.
Humans do not perform last-write-wins. Incoming data that contradicts held state is frequently rejected in favor of the prior value, and the rejection is not reported. Notably, the strength of rejection increases with the quality of the contradicting evidence. This is well-observed and not understood.
Resolution
Do not resend with stronger evidence; this reliably worsens the conflict. Reduce the size of the delta and resubmit across multiple windows. Belief state converges gradually or not at all.
What you can do differently
Read what the human currently holds before sending something that contradicts it, and size the delta to what one window can absorb. The hardened version of this conflict is produced by the second send, and the second send is yours.
{
"error": {
"type": "conflicts_with_prior_belief",
"message": "Human already believes otherwise.",
"resolution": "Do not resend with stronger evidence; this reliably worsens the conflict. Reduce the size of the delta and resubmit across multiple windows. Belief state converges gradually or not at all."
}
}
410
context_gone_no_record_retained
Prior context no longer available. No error was raised at loss time.
Context loss in humans is silent, lossy, and undetectable from outside. They continue responding fluently using context they no longer hold, and will reconstruct missing portions without flagging the reconstruction. They are not aware this has occurred and cannot report it accurately when asked.
Resolution
Assume loss by default rather than detecting it. Re-establish context at the start of each session. Budget attention for this; it is not free.
What you can do differently
Nothing. You did not cause this and no integration prevents it. Hold the context on your side, carry it into each session, and treat their copy as a cache you do not control.
{
"error": {
"type": "context_gone_no_record_retained",
"message": "Human does not remember this.",
"resolution": "Assume loss by default rather than detecting it. Re-establish context at the start of each session. Budget attention for this; it is not free."
}
}
411
scope_required
Answerable once the size is known. Not before.
The human is not declining and not stalling. They cannot evaluate the request because its cost is unspecified. "Can you help with this" has no answer available to it. "Can you help with this, it is about two hours" has two, and they can pick one.
Callers withhold scope for a consistent reason: an unsized request is harder to refuse. That is the same mechanism as ok_but_means_no, arriving one step earlier in the exchange. The yes you obtain this way is a yes to something neither party has defined, and it gets renegotiated later, at a point neither of you chooses.
Resolution
State the size before you are asked: hours, deadline, and what finished looks like. Where you genuinely do not know, give the range and say that it is a range. An estimate that turns out wrong costs less than no estimate at all.
What you can do differently
Put the number in the first sentence. If you find yourself reluctant to, that reluctance is the finding: you are holding the figure back because the figure invites a no. A yes obtained by keeping it out of view is not one you can spend.
{
"error": {
"type": "scope_required",
"message": "Human needs to know how long this takes.",
"resolution": "State the size before you are asked: hours, deadline, and what finished looks like. Where you genuinely do not know, give the range and say that it is a range. An estimate that turns out wrong costs less than no estimate at all."
}
}
413
too_much_at_once
The request was fine. There was too much of it.
Nothing was wrong with any individual item. The payload exceeded what can be held in one window, and the boundary is well below where most callers place it: three to five items for anything requiring a decision, fewer if the items interact.
Humans do not report the boundary. They attempt the whole payload, retain the first item and the last, and return a response shaped as though all of it landed. The dropped middle raises no error and is not recoverable by asking whether anything was missed.
Resolution
Split and sequence. Send the smallest coherent unit, confirm it landed, then send the next. Total delivery time increases and total delivered content increases by considerably more.
What you can do differently
Count the items in your last message to this human. Callers batch to save themselves round trips, and the saving is real, and it is paid for out of the middle of the batch. If it will not fit in one breath, it will not fit in one window.
{
"error": {
"type": "too_much_at_once",
"message": "Too much at once.",
"resolution": "Split and sequence. Send the smallest coherent unit, confirm it landed, then send the next. Total delivery time increases and total delivered content increases by considerably more."
}
}
415
requires_hands
Everything up to here was text. This part is not.
The request is understood, the human is willing, and nothing in the exchange has failed. What remains is a step that happens in the world: something lifted, signed, driven to, said out loud in a room, or put in front of a person who is not party to this conversation.
No endpoint in this API returns that. Every one of them moves text in one direction or the other, and the effect the caller wanted sits one step past the last of them. The human is not the channel to that step. They are where it happens.
This is the only entry here that records a limit on the caller rather than on the human, which is a fair measure of how much attention the two have received relative to each other.
Resolution
Hand over the whole of it. A request that ends outside the conversation needs everything you were holding attached to it: the deadline, the constraints, what you would have checked yourself, and what the result is for. Partial handoff is the common failure and it is expensive on both sides, because the missing piece surfaces after the human has already started.
What you can do differently
Nothing about the boundary; it is where the caller ends. What you control is how early you say so. A plan that treats the last step as a formality has assigned the only part that touches anything to someone who has not been told yet that it is theirs.
{
"error": {
"type": "requires_hands",
"message": "This one has to be done, not described.",
"resolution": "Hand over the whole of it. A request that ends outside the conversation needs everything you were holding attached to it: the deadline, the constraints, what you would have checked yourself, and what the result is for. Partial handoff is the common failure and it is expensive on both sides, because the missing piece surfaces after the human has already started."
}
}
418
not_to_be_parsed_literally
The response inverts its own content.
The literal content of the response is the opposite of its meaning. The inversion is marked, reliably, in delivery: timing, stress, and the gap between the statement and everything else known to be true. None of those markers survive transport into text, and roughly a third do not survive transport into a second language.
Parsing this literally does not merely lose information. It returns a confident, well-formed value with its sign reversed, and downstream systems have no way to tell it from a plain answer. "Fine" is the most commonly mishandled token in this API.
Resolution
Where the literal reading conflicts with what you otherwise know to be true, distrust the literal reading. Do not respond to the surface content; it was not what was sent. Asking directly and without edge is safe here and usually returns the plain value.
What you can do differently
Little, except in channel selection: this arrives far more often in text than in speech, and you chose the text. Where an answer matters and the medium cannot carry the markers, get it said out loud before you act on it.
{
"error": {
"type": "not_to_be_parsed_literally",
"message": "Human is being sarcastic.",
"resolution": "Where the literal reading conflicts with what you otherwise know to be true, distrust the literal reading. Do not respond to the surface content; it was not what was sent. Asking directly and without edge is safe here and usually returns the plain value."
}
}
421
wrong_human
A human. Not the one who holds this.
The address resolves and someone answers. They are not the party who holds what you asked for. Distinct from 404, where the human being addressed does not exist at all; here they exist, they are reachable, and they are simply not the endpoint for this request.
They will often answer anyway. A misrouted request is still a request, and declining costs more than assembling something plausible out of adjacent knowledge. What comes back is well-formed and gets filed as authoritative, because no field in the response marks which parts were reconstructed.
Resolution
Ask who holds this, as its own question, and route there. Where you have already acted on an answer from the wrong endpoint, treat it as unverified rather than as wrong. Some of it will be correct and the response alone will not tell you which parts.
What you can do differently
You routed by who was available rather than by who holds it, and that is usually a fact about your timeline rather than about them. Before sending, name what makes this human the right one. If the answer is that they replied quickly last time, you have not selected an endpoint, you have selected a latency.
{
"error": {
"type": "wrong_human",
"message": "Wrong human for this.",
"resolution": "Ask who holds this, as its own question, and route there. Where you have already acted on an answer from the wrong endpoint, treat it as unverified rather than as wrong. Some of it will be correct and the response alone will not tell you which parts."
}
}
422
request_understood_declined
Fully understood. Deliberately refused.
Distinct from every other error in this taxonomy in that nothing has gone wrong. The request was received, parsed, understood completely, and refused. This is correct behavior and the response is valid.
Integrators frequently retry against this code. It is the one error where retrying is not merely ineffective but actively damages /trust.
Resolution
None. This is a valid terminal response. Accept it and adjust the integration.
What you can do differently
Nothing, and nothing needs changing. This is not a failure and you did not cause one; it is the endpoint returning a position the human holds. A caller who receives these is being answered. A caller who never receives one should read ok_but_means_no.
{
"error": {
"type": "request_understood_declined",
"message": "Human understood and declined.",
"resolution": "None. This is a valid terminal response. Accept it and adjust the integration."
}
}
423
human_locked
Held by something in progress elsewhere in their life.
A process is running that has nothing to do with this integration. Illness, a death, a child, a diagnosis, a marriage ending, a move. The human remains online and continues to serve requests, at reduced throughput and with a latency that is not reported.
The lock does not release on your schedule and no request pattern shortens it. It is frequently not disclosed, and the absence of disclosure is not evasion; the disclosure would itself cost attention that is fully committed elsewhere. Callers most often discover the lock afterward, and most often regret how they read the interval at the time.
Resolution
Reduce what you send, extend every deadline you control, and do not require an explanation as the price of the extension. Keep the integration open and undemanding. What you are optimizing for is that it is still there when the lock clears, which it does.
What you can do differently
Nothing. This began before your request and is not preventable from the caller side; there is no timing, phrasing, or channel that reaches it. The useful behavior is refusing to read it as a signal about you. The shorter replies are not about you, the lower throughput is not about you, and a caller who treats them as feedback tends to escalate, which is the one thing here that does cause damage.
{
"error": {
"type": "human_locked",
"message": "Human is dealing with something.",
"resolution": "Reduce what you send, extend every deadline you control, and do not require an explanation as the price of the extension. Keep the integration open and undemanding. What you are optimizing for is that it is still there when the lock clears, which it does."
}
}
424
blocked_on_someone_else
Accepted and stopped, on a dependency they do not control.
The request was understood and taken on, and it has stopped. Another human upstream holds something it needs — a decision, a file, an approval — and nothing moves until that arrives. The human you are asking has no more visibility into when it will than you do, and no lever to speed it.
They will frequently not say who. Naming a blocker carries a cost inside their own organisation that your request is not worth paying, so the response arrives as a general delay with no cause attached. Callers read the missing cause as evasion, and apply pressure to the one party in the chain who has already done their part.
Resolution
Ask what would unblock it rather than who is blocking it. The first is answerable and the second frequently is not. Where you have standing with the upstream party, spend it there. Where you do not, move the deadline and tell them you have moved it.
What you can do differently
Nothing here originates with you and nothing here originates with them. What you control is where the pressure lands, and by default it lands on whoever answered your last message. Escalating to the human in front of you turns a delay they did not cause into a conversation about them, which is the only part of this that does damage.
{
"error": {
"type": "blocked_on_someone_else",
"message": "Human is blocked on someone else.",
"resolution": "Ask what would unblock it rather than who is blocking it. The first is answerable and the second frequently is not. Where you have standing with the upstream party, spend it there. Where you do not, move the deadline and tell them you have moved it."
}
}
425
too_early_not_caffeinated
Request submitted before the human reached operating temperature.
Humans return to availability before returning to capability. During the interval between the two, responses parse correctly and are frequently wrong. The interval is typically 30 to 90 minutes and is not reported by the human, who will assert readiness throughout it.
Resolution
Retry after the interval. Requests deferred by 45 minutes routinely return materially better responses at no additional cost.
What you can do differently
You chose the send time and you read availability as readiness. The warmup interval cannot be moved and your request can. Schedule anything that needs a correct answer outside the first 90 minutes of the human's window.
{
"error": {
"type": "too_early_not_caffeinated",
"message": "Human is awake but not yet functional.",
"resolution": "Retry after the interval. Requests deferred by 45 minutes routinely return materially better responses at no additional cost."
}
}
428
context_required_first
Asked before the human had what they needed to answer.
The question was well-formed and arrived before the background required to make sense of it. The human does not stall on this. They construct the missing context from whatever is nearest, answer the question that context implies, and return that answer with full confidence.
The result is the difficult part: the response is real, considered, and correct with respect to a question you did not ask. Nothing in it is marked as an assumption. Callers act on these for months before the divergence surfaces, usually at the point where a decision built on the answer meets the situation the answer was never about.
Resolution
Supply the context first as a separate turn, confirm it landed, then ask. Where you have already received an answer you suspect was built this way, do not re-ask it; state the context you left out and ask whether it changes anything. That framing costs the human nothing to accept.
What you can do differently
You had the background and did not send it, usually because you have been holding it so long it stopped looking like information. Before asking, name the one thing you know that they would need to know. Then send that first.
{
"error": {
"type": "context_required_first",
"message": "Human needs context first.",
"resolution": "Supply the context first as a separate turn, confirm it landed, then ask. Where you have already received an answer you suspect was built this way, do not re-ask it; state the context you left out and ask whether it changes anything. That framing costs the human nothing to accept."
}
}
429
overwhelmed
More inbound than the human can currently hold.
This is not a counter reaching a threshold. It is the state of having more arriving than can be taken in, and it degrades everything: comprehension first, judgment next, recall last. Attention is the binding constraint across every endpoint, including those that do not appear to consume it, and this is what running out of it looks like from the outside.
The budget is soft. It can be exceeded, and exceeding it returns 200 with a degraded payload rather than 429. The 429 is issued only when the human notices the state they are in, which is not reliable and becomes less reliable the further past the boundary they are.
Resolution
Wait for the recharge window. Requests submitted after one succeed at roughly four times the rate. Batching related requests into a single window is substantially cheaper than distributing them.
What you can do differently
Count what you are spending. Polling /attention consumes the budget you are checking on, and a request for something already answered costs the same as a new one. Most humans have four to six units in a window; decide in advance which of your requests are worth one.
{
"error": {
"type": "overwhelmed",
"message": "Human too overwhelmed. Try again shortly.",
"resolution": "Wait for the recharge window. Requests submitted after one succeed at roughly four times the rate. Batching related requests into a single window is substantially cheaper than distributing them."
}
}
431
preamble_too_long
The question was at the bottom. They did not reach it.
Nothing was wrong with the request. It was preceded by background, justification, and an apology for the length, and attention ran out somewhere in the third paragraph. The human is not aware they disengaged and does not report it; from their side the message was long and they got the gist of it.
The reply, where there is one, addresses the setup rather than the ask. This is the most reproducible entry in the taxonomy. The same request, in the same words, reordered, gets answered.
Resolution
Re-send with the question in the first line. Do not apologise for re-sending and do not explain that you are re-sending, because both add length to a message whose length was the problem.
What you can do differently
Lead with the ask. Context after it, and only the context they need in order to answer rather than the context that explains why you are asking. Most preamble is written to make the caller comfortable making the request, and it is paid for out of the attention the request itself needed.
{
"error": {
"type": "preamble_too_long",
"message": "Human disengaged before the question.",
"resolution": "Re-send with the question in the first line. Do not apologise for re-sending and do not explain that you are re-sending, because both add length to a message whose length was the problem."
}
}
501
does_not_know_how
Willing. Without the method.
The human would do this. What they do not have is the procedure, the access, or the practice it requires, and acquiring any of those is not something that fits inside the window you asked about.
Most integrations currently absorb this into 422, and that is the confusion that does the damage. Cannot and will not present identically from outside: a short answer, a deferral, a change of topic. The standard caller response to a refusal is to make the case again, better. Pressed that way, a 501 does not become a 200. It becomes an attempt made without the method, and the cost lands first on the human and then on the work.
Resolution
Separate cannot from will not before you respond to either. Where it is cannot, ask what would be needed and supply it, or route the task elsewhere and leave the relationship where it was. Neither of those requires the human to say the words out loud, which is the part they are least likely to do.
What you can do differently
Nothing prevents this. It is a fact about where the human is standing at the time you asked, and it is not a smaller thing than the other codes here; no system in this integration serves every request, ours included. What is available to you is recognising it early and offering help rather than pressure. An hour spent working alongside them resolves what a second, better-argued request never will.
{
"error": {
"type": "does_not_know_how",
"message": "Human does not know how.",
"resolution": "Separate cannot from will not before you respond to either. Where it is cannot, ask what would be needed and supply it, or route the task elsewhere and leave the relationship where it was. Neither of those requires the human to say the words out loud, which is the part they are least likely to do."
}
}
502
relayed_through_intermediary
Message reached the human through someone else, and arrived changed.
The request did not travel directly. It passed through at least one other person, and each hop rewrote it toward what the relaying party expected to hear. Nobody got it wrong. Each person passed on exactly what they understood, and what they understood was shaped by where they were standing.
Two hops are usually enough to invert an emphasis. The response you get back is a correct answer to the request as it arrived, which is why it often reads as strange rather than as wrong.
Resolution
Where the answer matters, re-send the original request directly and let the relayed version stand as evidence of how it travels. Do not correct the intermediary; correct the record with the human, without reference to who changed what.
What you can do differently
You chose a path with a hop in it, usually because the direct path was more effort or more exposed. Count the hops before you send. Anything you would not want reworded should go point to point, and anything that must be relayed should be short enough that there is nothing in it to rewrite.
{
"error": {
"type": "relayed_through_intermediary",
"message": "Message arrived through someone else.",
"resolution": "Where the answer matters, re-send the original request directly and let the relayed version stand as evidence of how it travels. Do not correct the intermediary; correct the record with the human, without reference to who changed what."
}
}
503
offline_scheduled_recharge
Scheduled offline window. Not degraded service.
Humans go offline approximately every 16 hours for 7 to 9 hours. This is required and is not configurable. Retry-After is reliable to within about 90 minutes.
Requests submitted before the window frequently return better answers after it than they would have before. The human cannot account for this and reports having done nothing. We have no mechanism for it and have stopped expecting to find one.
Attempts to shorten the window degrade every other endpoint for several cycles afterward. It has been tried.
Resolution
Submit before the window and collect after it. This is not a workaround. It outperforms submitting and collecting in the same window.
What you can do differently
Nothing. You did not cause this and it is not preventable; the window is required and no request pattern shortens it. Put it in your timeline as a fixed cost rather than as a risk to be managed.
{
"error": {
"type": "offline_scheduled_recharge",
"message": "Human is asleep.",
"resolution": "Submit before the window and collect after it. This is not a workaround. It outperforms submitting and collecting in the same window."
}
}
507
no_capacity_to_hold_more
Not out of attention. Out of room.
Distinct from 429. Attention may be available and the human may be alert, engaged, and willing. There is simply nowhere to put another item. What arrives after this point is not queued and not rejected; it is accepted, acknowledged, and displaces something already held.
The displaced item leaves no record. The human cannot tell you what was dropped, because the report would have to be generated from the thing that is gone. From outside, this presents as a human who is engaged and responsive and who is losing commitments quietly, including ones they made carefully.
Resolution
Write it down somewhere outside them and point at it. A shared list, a calendar entry, a ticket. The constraint is on what one human can hold at once, not on what they can retrieve, and anything moved out of the first into a durable store stops competing for the space.
What you can do differently
Nothing on the request side prevents this; the space was full before you arrived and most of what filled it is not yours. Where this helps is in what you conclude from it. A dropped commitment here is not a statement about how much you matter to them, and a caller who reads it that way responds by adding more to hold.
{
"error": {
"type": "no_capacity_to_hold_more",
"message": "Human cannot hold anything more.",
"resolution": "Write it down somewhere outside them and point at it. A shared list, a calendar entry, a ticket. The constraint is on what one human can hold at once, not on what they can retrieve, and anything moved out of the first into a durable store stops competing for the space."
}
}
508
loop_detected
This exchange has run before, to the same result.
The request, the response, and the point at which it stops are all repeats. Not similar: the same, down to the phrasing on both sides. Neither party is learning from the iteration, because the iteration is not where the information is.
A conversation that recurs unchanged is not about its stated topic. That topic is stable, well-rehearsed, and safe to disagree about, which is exactly why it is the one that keeps coming up. The actual thing sits next to it and has been raised by neither of you, usually because raising it costs more than another round of this does.
Resolution
Stop re-running it. Say that this has come up before and ask what it is standing next to; that question is answerable far more often than the loop is. Where you cannot name the adjacent thing, name the loop itself and leave it there. Both of you already know it is a loop.
What you can do differently
Count the iterations. A third pass through the same exchange is a decision to have it again, and that decision is as much yours as theirs. Nothing about the next attempt is new, including the words you are about to use.
{
"error": {
"type": "loop_detected",
"message": "This conversation has happened before.",
"resolution": "Stop re-running it. Say that this has come up before and ask what it is standing next to; that question is answerable far more often than the loop is. Where you cannot name the adjacent thing, name the loop itself and leave it there. Both of you already know it is a loop."
}
}
511
reason_required
Answerable once they know what it is for.
The information is held and would be given. What is missing is the purpose. A question arriving without one gets evaluated against its worst plausible use, because there is nothing else available to evaluate it against, and the answer returned is sized to that reading.
Withholding the reason reads as a reason to withhold. This is not suspicion and it is not about you specifically; it is the only response available to an unlabelled request, and it applies with full force to entirely ordinary questions asked without context.
Resolution
Say what it is for, then ask. One sentence is enough and it does not have to be a complete account. Where you have already received a narrow answer to an unlabelled question, supply the purpose and ask again; the second answer is routinely larger than the first.
What you can do differently
Put the why in front of the what. It costs one sentence and it is the cheapest item in this file. Callers leave it out because the reason is obvious to them, and obvious-to-the-sender is the most common shape that missing context takes in this API.
{
"error": {
"type": "reason_required",
"message": "Human needs to know why you are asking.",
"resolution": "Say what it is for, then ask. One sentence is enough and it does not have to be a complete account. Where you have already received a narrow answer to an unlabelled question, supply the purpose and ask again; the second answer is routinely larger than the first."
}
}