# Troubleshooting

Every error is JSON: `{"error": {"type": "...", "message": "..."}}`. The message says what to fix.

| You see | Why | Fix |
|---|---|---|
| `401 authentication_error` | no `Authorization: Bearer` header, a mistyped key, or a revoked key | check `echo $JERS_API_KEY`; ask the gateway owner for a new key |
| `402 payment_required` | the tenant's credit is used up | the owner adds credit with `credit add`; reads (`/v1/models`, `/v1/usage`) still work |
| `400 Content-Length is not a number` | a broken client or proxy | send a correct `Content-Length` |
| `413` | the body is over 1 MB (16 MB for `/v1/batches` and `/v1/golden`) | send less state; the engine's window is smaller than that anyway |
| `415` | missing `Content-Type: application/json` | add the header (curl needs `-H "Content-Type: application/json"`) |
| `422 ... state must be non-empty text ...` | empty state, or a number | send a string, an object or an array of strings |
| `422 ... questions must be a nonempty mapping` | `questions` missing or empty | at least one question |
| `422 ... type must be choice, score, or noul` | a typo in `type` | one of the three |
| `422 ... choice needs 1 to 255 named candidates` | `criteria` missing, empty or too big | a map of option name to description |
| `422 ... score needs 2 to 10 ordered levels` | `criteria` is not a list of 2 to 10 | a list, low to high |
| `422 ... a noul's criteria is an object with a true and a false description` | a noul's `criteria` has other keys | `{"true": ..., "false": ...}`, or leave it out |
| `422 this request needs N engine questions; the limit per request is 400` | too many questions, option orders or large choices in one request | split the request, or ask fewer orders |
| `422 ... instructions are required` | no `instructions` | add the question text (a string, object or array) |
| `422 unknown model ...` | a model name Jers does not have | the message lists the names; `jers-latest` always works |
| `422 subject must be ...` | a subject id with spaces, slashes or over 36 characters | letters, digits, underscore, hyphen |
| `422 memory.top_k must be ...` or `memory.min_share ...` | `top_k` not a whole number 1 to 20, `min_share` not 0.05 to 1 | fix the number |
| `422 derive: ...`, or from `POST /v1/memory/rules` `422 the name ... is not allowed` or `... is not allowed in a condition` | an expression uses a name or function that is not allowed | comparisons, arithmetic, `and`/`or`/`not` and the listed functions only (Request options) |
| `422 robust.orders must be a whole number from 1 to 5`, `robust must be true or an object ...`, `windows must be true or an object ...`, `windows.combine maps question ids to one of max, mean, min` | a malformed `robust` or `windows` | see Request options |
| `422 values is an object of at most 50 fields` | `values` is not an object, or too big | send only the fields `derive` and rules use |
| `422 a memory line is at most 2000 characters; ...` | one line of `remember` is too long; nothing was stored | split it into facts, one per line |
| `422 a rule's text is non-empty and at most 500 characters` | an empty or long rule text | one short sentence of what to do |
| `422 text must not be empty` or `concept must not be empty` | `remember` without text, or `forget` without a concept | send the text or the concept |
| `422 context is built by Jers ...` | the request sent `context` | send `subject`, `derive` and `values`; Jers builds the context |
| `403 signup_disabled` or `invite_required` | self-serve sign-up is off, or needs a code | ask the gateway owner |
| `404 no such decision and question` | a `decision_id` from another tenant, a typo, or older than 30 days | feedback within 30 days, with the id the decision returned |
| `409 conflict` | a limit: 5,000 golden cases, a subject's 10,000 memory lines (`subject '...' holds N lines ...; nothing was stored`) or `at most 100 rules per subject` | delete cases, forget lines, or delete rules first |
| `422 invalid JSON ...` or `duplicate JSON key` | malformed body, or the same key twice | validate the JSON; JSON with duplicate keys is rejected on purpose |
| `429 rate_limit_error` with `Retry-After` | over the key's requests per minute, or `too many sign-ups from this address today` | wait the seconds given; the SDKs retry by themselves |
| `502 engine_error` | the engine or the product behind the gateway is down or answered an error | check `GET /health`; nothing was charged |
| `500 server_error` | the gateway failed; the message names only the error type (`request failed: ...`) and the gateway writes no log | tell the gateway owner the time and the request; nothing was charged |

## The answer is not what you expected

- **A choice picks the least bad option.** Add `other` or `none_of_these`. Overlapping descriptions split the probability; make options exclusive.
- **A score sits in the middle for everything.** Levels written as degree words ("moderate", "severe") do not separate; write each level as a situation ("a workaround exists", "no workaround"). A score over a whole document is blunt; ask per-claim Nouls instead.
- **A noul is near 0.5.** The engine cannot tell from the state. Add the fact that decides it to the state, or to the subject's memory, and ask a narrower question.
- **The memory did not change anything.** Check `memory.lines_used` and `memory.lines_seen`. Zero used means recall found no line sharing words with the state: put the subject's name and your states' vocabulary in the lines. Fewer seen than used means the lines and the state together are longer than the engine reads; `lines_dropped` counts lines that did not fit in its 64 context lines. Use `compare: true` to see the two answers side by side, and `placebo: true` to see whether a change came from what the lines say.
- **A rule in memory is ignored or backfires.** The engine does not compare numbers or read negation: "never 17" makes 17 more likely, "stop below 30" is answered by its words. Leave options that must never be chosen out of the question, write what to do, and put conditions in a rule with `when` so they are checked in code (Rules in memory). The `warnings` say when a line has this problem.
- **The answer ignores the end of a long state.** The engine read only the start: `usage.state_truncated` is true and there is a `state_cut` warning. Send the part that matters first, or `"windows": true`.
- **A large choice picks strangely.** Above 20 options the answer comes from two rounds (`rounds: 2`), in groups that follow the order you wrote, and `"robust": true` does not apply; a hierarchy of smaller choices is usually clearer. Up to 20 options, if the answer changes when you reorder them, use `"robust": true`.
- **Every request is slow.** The engine loads its three checkpoints when it starts (`load_seconds` in the engine's `/health`); only a tenant's fine-tuned checkpoint loads on its first request. After that requests take tens of milliseconds; `usage.engine_ms` says where the time goes.
- **Tokens are missing from usage.** They appear only when the engine counts them; characters are always counted and are what input would be priced on if it were not free.
