# Handle every verdict

> Client code that branches correctly on all three outcomes and on the refusals around them.

The mistake to avoid is collapsing three different situations into one
`try/except`. A refusal, an outage and a spent allowance need three different
responses, and two of them should never reach a retry loop.

## The shape

```python
import httpx

def ask(intent: str, oracle: str = "chart") -> dict:
    r = httpx.post(
        f"{BASE}/v1/oracles/{oracle}/ask",
        headers={"Authorization": f"Bearer {KEY}"},
        json={"intent": intent},
        timeout=120,                       # a verdict can take this long
    )

    # ---- refusals: the request never reached an oracle -----------------
    if r.status_code == 401:
        # KEY_MISSING / KEY_UNKNOWN / KEY_REVOKED / KEY_EXPIRED.
        # A human has to act. Never retry.
        raise Fatal(r.json()["error"])
    if r.status_code == 402:
        # QUOTA_EXCEEDED. The credential is fine; the allowance is spent.
        # A different key will not help — quota is per organisation.
        raise OutOfCredits(r.json()["context"])
    if r.status_code == 404:
        raise Fatal("no such oracle — read GET /v1/oracles")
    if r.status_code == 422:
        # Includes "this oracle cannot produce that format", and the
        # response names the ones it can.
        raise Fatal(r.json())
    r.raise_for_status()

    # ---- verdicts: an oracle ran -------------------------------------
    answer = r.json()
    if answer["status"] == "error":
        return retry_with_backoff()        # ours, unbilled
    if answer["status"] == "invalid":
        return rejected(answer["detail"])  # billed, and a real answer
    return answer["result"]                # store verified_by with it
```

## The distinctions that matter

**`402` is not `401`.** Your credential is fine. A client that treats a spent
quota as an auth failure re-prompts a human for a key that was never the problem.

**`invalid` is not an exception.** It is the product working. Surface it to
whoever asked, do not swallow it, and do not retry the identical intent hoping
for a different roll.

**`error` is the only thing worth retrying automatically**, and it is free.

## Store the citation

```python
save(artifact=answer["result"], checked_against=answer["verified_by"])
```

An artifact without its `verified_by` is just a file. See
[Citations](https://docs.formalogic.io/citations).

## If an agent is the caller

The [MCP tools](https://docs.formalogic.io/mcp) do the transport half of this for you and hand your
agent structured `{"error": {"kind": …}}` objects it can reason about.
