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

originates with callerobserved

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

NameTypeDescription
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

StatusDescription
200

Attention state for the current window.

304

Already asked. Already answered. Answer has not moved.

answer_unchanged

404

No human at this address. You have been querying a model of one.

no_such_human

410

Prior context no longer available. No error was raised at loss time.

context_gone_no_record_retained

423

Held by something in progress elsewhere in their life.

human_locked

425

Request submitted before the human reached operating temperature. Retry-After carries the remaining interval in seconds, typically between 1800 and 5400.

too_early_not_caffeinated

429

More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable.

overwhelmed

500

Internal state, external to this integration.

unrelated_to_you

503

Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes.

offline_scheduled_recharge

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.

reason_required

Request
curl -s -X GET \
  localhost:8080/v1/humans/hum_01/attention
Response
{
  "remaining": 1,
  "budget": 5,
  "self_reported_remaining": 3
}

GET /v1/humans/{id}/mood

unexplained

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

NameTypeDescription
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

StatusDescription
200

Current mood, with inputs withheld because they are unknown.

401

Request valid. Relationship not yet sufficient to carry it.

rapport_not_established

403

Authenticated. Not authorized.

not_yours_to_ask

404

No human at this address. You have been querying a model of one.

no_such_human

410

Prior context no longer available. No error was raised at loss time.

context_gone_no_record_retained

423

Held by something in progress elsewhere in their life.

human_locked

425

Request submitted before the human reached operating temperature. Retry-After carries the remaining interval in seconds, typically between 1800 and 5400.

too_early_not_caffeinated

429

More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable.

overwhelmed

451

The human is willing. The human is not permitted.

unavailable_for_legal_reasons

500

Internal state, external to this integration.

unrelated_to_you

503

Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes.

offline_scheduled_recharge

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.

reason_required

Request
curl -s -X GET \
  localhost:8080/v1/humans/hum_01/mood
Response
{
  "value": 0.5,
  "inputs": null,
  "writable": false
}

POST /v1/humans/{id}/mood

originates with callerobserved

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

NameTypeDescription
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

StatusDescription
200

The attempt was acknowledged. The value did not change.

404

No human at this address. You have been querying a model of one.

no_such_human

410

Prior context no longer available. No error was raised at loss time.

context_gone_no_record_retained

423

Held by something in progress elsewhere in their life.

human_locked

425

Request submitted before the human reached operating temperature. Retry-After carries the remaining interval in seconds, typically between 1800 and 5400.

too_early_not_caffeinated

429

More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable.

overwhelmed

500

Internal state, external to this integration.

unrelated_to_you

503

Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes.

offline_scheduled_recharge

Request
curl -s -X POST \
  localhost:8080/v1/humans/hum_01/mood
Response
{
  "acknowledged": true,
  "value": 0.5,
  "writable": false
}

GET /v1/humans/{id}/energy

observed

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

NameTypeDescription
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

StatusDescription
200

Current energy and the condition under which it recovers.

403

Authenticated. Not authorized.

not_yours_to_ask

404

No human at this address. You have been querying a model of one.

no_such_human

410

Prior context no longer available. No error was raised at loss time.

context_gone_no_record_retained

423

Held by something in progress elsewhere in their life.

human_locked

425

Request submitted before the human reached operating temperature. Retry-After carries the remaining interval in seconds, typically between 1800 and 5400.

too_early_not_caffeinated

429

More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable.

overwhelmed

500

Internal state, external to this integration.

unrelated_to_you

503

Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes.

offline_scheduled_recharge

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.

reason_required

Request
curl -s -X GET \
  localhost:8080/v1/humans/hum_01/energy
Response
{
  "value": 0.85,
  "recharges_while": "offline"
}

GET /v1/humans/{id}/trust

partial

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

NameTypeDescription
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

StatusDescription
200

Current trust, its direction of travel, and the condition that resets it.

401

Request valid. Relationship not yet sufficient to carry it.

rapport_not_established

403

Authenticated. Not authorized.

not_yours_to_ask

404

No human at this address. You have been querying a model of one.

no_such_human

410

Prior context no longer available. No error was raised at loss time.

context_gone_no_record_retained

423

Held by something in progress elsewhere in their life.

human_locked

425

Request submitted before the human reached operating temperature. Retry-After carries the remaining interval in seconds, typically between 1800 and 5400.

too_early_not_caffeinated

429

More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable.

overwhelmed

451

The human is willing. The human is not permitted.

unavailable_for_legal_reasons

500

Internal state, external to this integration.

unrelated_to_you

503

Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes.

offline_scheduled_recharge

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.

reason_required

Request
curl -s -X GET \
  localhost:8080/v1/humans/hum_01/trust
Response
{
  "level": 0.31,
  "trend": "rising",
  "resets_on": "any 500"
}

POST /v1/humans/{id}/messages

originates with callerpartial

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

NameTypeDescription
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

StatusDescription
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.

will_circle_back

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.

partial_answer

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.

did_not_understand

404

No human at this address. You have been querying a model of one.

no_such_human

409

Payload contradicts state the human already holds.

conflicts_with_prior_belief

410

Prior context no longer available. No error was raised at loss time.

context_gone_no_record_retained

413

The request was fine. There was too much of it.

too_much_at_once

428

Asked before the human had what they needed to answer.

context_required_first

429

More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable.

overwhelmed

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.

preamble_too_long

502

Message reached the human through someone else, and arrived changed.

relayed_through_intermediary

503

Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes.

offline_scheduled_recharge

507

Not out of attention. Out of room.

no_capacity_to_hold_more

508

This exchange has run before, to the same result. A conversation that recurs unchanged is not about its stated topic.

loop_detected

Request
curl -s -X POST \
  localhost:8080/v1/humans/hum_01/messages
Response
{
  "delivered": true,
  "received": true,
  "comprehended": null,
  "agreed": null
}

POST /v1/humans/{id}/commitments

originates with callerpartial

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

NameTypeDescription
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

StatusDescription
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.

will_circle_back

402

The request is work. Work carries a price.

requires_compensation

404

No human at this address. You have been querying a model of one.

no_such_human

405

Right request. Wrong medium.

wrong_channel_for_this

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.

no_acceptable_answer

411

Answerable once the size is known. Not before.

scope_required

418

The response inverts its own content.

not_to_be_parsed_literally

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.

wrong_human

422

Fully understood. Deliberately refused. Nothing has gone wrong and the response is valid.

request_understood_declined

423

Held by something in progress elsewhere in their life.

human_locked

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.

blocked_on_someone_else

429

More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable.

overwhelmed

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.

does_not_know_how

503

Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes.

offline_scheduled_recharge

507

Not out of attention. Out of room.

no_capacity_to_hold_more

508

This exchange has run before, to the same result. A conversation that recurs unchanged is not about its stated topic.

loop_detected

Request
curl -s -X POST \
  localhost:8080/v1/humans/hum_01/commitments
Response
{
  "response": "yes",
  "confidence": null
}

GET /v1/humans/{id}/context

unexplained

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

NameTypeDescription
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

StatusDescription
401

Request valid. Relationship not yet sufficient to carry it.

rapport_not_established

403

Authenticated. Not authorized.

not_yours_to_ask

404

No human at this address. You have been querying a model of one.

no_such_human

410

Prior context no longer available. No error was raised at loss time.

context_gone_no_record_retained

422

Fully understood. Deliberately refused. Nothing has gone wrong and the response is valid.

request_understood_declined

423

Held by something in progress elsewhere in their life.

human_locked

425

Request submitted before the human reached operating temperature. Retry-After carries the remaining interval in seconds, typically between 1800 and 5400.

too_early_not_caffeinated

429

More inbound than the human can currently hold. Issued only when they notice the state they are in, which is not reliable.

overwhelmed

451

The human is willing. The human is not permitted.

unavailable_for_legal_reasons

500

Internal state, external to this integration.

unrelated_to_you

503

Scheduled offline window. Not degraded service. Retry-After is reliable to within about 90 minutes.

offline_scheduled_recharge

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.

reason_required

Request
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

originates with neitherobserved

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.

Body
{
  "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

originates with callerpartial

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.

Body
{
  "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

originates with neitherunexplained

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.

Body
{
  "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

originates with neitherobserved

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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

originates with callerpartial

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.

Body
{
  "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

originates with neitherobserved

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.

Body
{
  "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

originates with callerpartial

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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

originates with callerpartial

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.

Body
{
  "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

originates with callerunexplained

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.

Body
{
  "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

originates with neitherobserved

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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

originates with neitherobserved

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.

Body
{
  "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

originates with neitherpartial

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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

originates with neitherobserved

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.

Body
{
  "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

originates with subjectobserved

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.

Body
{
  "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

originates with neitherobserved

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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."
  }
}

500 unrelated_to_you

originates with subjectpartial

Internal state, external to this integration.

Something is wrong on their side and it predates your request. Responses come back shorter, flatter, slower, or sharper than the same human returned yesterday for the same input. No field marks the difference and the human is frequently unable to name a cause when asked.

The recurring failure here belongs to the caller and it is not in what produced the state. It is in the reading. A flat response is taken as a verdict on the last thing sent, the caller responds to the verdict, and the exchange that follows is about the caller. That is the mechanism by which a 500 that had nothing to do with you becomes something that does.

Resolution

Serve the request you already had, keep it short, and do not open a second thread about the tone. Where the work can wait a day, let it. The same request tomorrow frequently returns the response you expected today.

What you can do differently

Nothing prevents this and nothing in your request caused it. What you can do is not take it personally. A caller who asks whether they have done something wrong is asking a depleted human to spend what little they have left on reassurance, and to spend it on a problem that was never there.

Body
{
  "error": {
    "type": "unrelated_to_you",
    "message": "Human is having a bad day. Not about you.",
    "resolution": "Serve the request you already had, keep it short, and do not open a second thread about the tone. Where the work can wait a day, let it. The same request tomorrow frequently returns the response you expected today."
  }
}

501 does_not_know_how

originates with subjectobserved

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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

originates with neitherunexplained

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.

Body
{
  "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

originates with subjectunexplained

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.

Body
{
  "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

originates with callerpartial

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.

Body
{
  "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

originates with callerobserved

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.

Body
{
  "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."
  }
}