> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heretic.quest/llms.txt
> Use this file to discover all available pages before exploring further.

# Read and Interpret Heretic Explain Endpoint Responses

> Call the explain endpoint with a session ID to receive a full verdict JSON. Learn how to parse the response and extract actionable signals.

Once your backend receives a session ID from the client, you exchange it for a verdict by calling Heretic's explain endpoint server-side. The endpoint returns a structured JSON object containing the overall ruling, a human-readable summary, the evidence families that contributed to the decision, and a flat list of individual signals with stable IDs you can key policy on. Because the call happens on your origin — no DNS changes, no reverse proxy required — the round trip adds only a lightweight server-to-edge fetch to your existing request handling.

## Authentication

Pass your Heretic API key as a Bearer token in the `Authorization` header of every request to the explain endpoint.

```http theme={null}
Authorization: Bearer hrtc_live_xxxxxxxxxxxxxxxxxxxx
```

Store the key in an environment variable (e.g. `HERETIC_API_KEY`). Never expose it in client-side code.

## Calling the Explain Endpoint

```
GET https://edge.heretic.quest/e?n={sessionId}
```

<CodeGroup>
  ```ts TypeScript (fetch) theme={null}
  const response = await fetch(
    `https://edge.heretic.quest/e?n=${sessionId}`,
    {
      headers: {
        Authorization: `Bearer ${process.env.HERETIC_API_KEY}`,
      },
    }
  );

  if (!response.ok) {
    // Handle 401, 404, 429 — see Error Responses below
    throw new Error(`Heretic explain error: ${response.status}`);
  }

  const verdict = await response.json();
  ```

  ```ts Node.js (node-fetch / undici) theme={null}
  import fetch from 'node-fetch'; // or: import { fetch } from 'undici';

  const verdict = await fetch(
    `https://edge.heretic.quest/e?n=${sessionId}`,
    {
      headers: {
        Authorization: `Bearer ${process.env.HERETIC_API_KEY}`,
      },
    }
  ).then((res) => {
    if (!res.ok) throw new Error(`${res.status}`);
    return res.json();
  });
  ```
</CodeGroup>

## Sample Response

```json theme={null}
{
  "verdict": "contradicted",
  "conclusive": true,
  "summary": "Claimed locale is physically unreachable; transport stack contradicts the claimed OS.",
  "contradicting_families": ["network-geometry", "transport-stack"],
  "families": { "measured": 6, "reporting": 6 },
  "signals": [
    {
      "id": "geo.rtt-below-vacuum",
      "tier": "composite",
      "headline": "RTT 2.1ms; light needs 27.4ms"
    },
    {
      "id": "stack.os-contradiction",
      "tier": "composite",
      "headline": "SYN by Linux 5.x; UA claims macOS 15"
    }
  ]
}
```

## Understanding the Response Fields

<ResponseField name="verdict" type="string">
  The overall ruling for the session. One of five values:

  | Value            | Meaning                                                                      |
  | ---------------- | ---------------------------------------------------------------------------- |
  | `contradicted`   | At least one signal family directly contradicts a claim the browser made.    |
  | `refused`        | The browser refused to supply enough information to reach a positive ruling. |
  | `disputed`       | Evidence is inconsistent but not conclusively contradictory.                 |
  | `uncontradicted` | No contradictions detected; evidence is internally consistent.               |
  | `insufficient`   | Too few signals were gathered to form a reliable ruling.                     |
</ResponseField>

<ResponseField name="conclusive" type="boolean">
  `true` when the evidence reaches the bar for hard enforcement — either a single absolute signal or multiple composite signals that corroborate each other. `false` indicates the verdict is directionally informative but not yet absolute.
</ResponseField>

<ResponseField name="summary" type="string">
  A single English sentence describing the primary reason for the verdict. Suitable for logging; **do not display it verbatim to end users.**
</ResponseField>

<ResponseField name="contradicting_families" type="string[]">
  The signal families that produced contradictions. Empty when `verdict` is not `contradicted`. Use this to tailor UI messaging (for example, surface a regional restriction notice when the list includes `network-geometry`).
</ResponseField>

<ResponseField name="families" type="object">
  Aggregate counts: `measured` is the number of families the collector attempted; `reporting` is the number that returned usable data.
</ResponseField>

<ResponseField name="signals" type="Signal[]">
  Flat list of individual signals the collector gathered. Each signal has:

  * `id` — stable dot-namespaced identifier (e.g. `geo.rtt-below-vacuum`). Safe to hard-code in policy rules.
  * `tier` — evidence strength: `composite` signals require cross-family corroboration; other tiers indicate standalone or weaker evidence.
  * `headline` — short human-readable description of what the signal measured.
</ResponseField>

## Error Responses

Handle these three error statuses before attempting to deserialize the response body as a verdict.

<AccordionGroup>
  <Accordion title="401 — Invalid or missing API key">
    The `Authorization` header was absent, malformed, or the key has been revoked. Verify the key at [heretic.quest/dashboard/keys](https://heretic.quest/dashboard/keys) and confirm your environment variable is set correctly in your deployment environment.
  </Accordion>

  <Accordion title="404 — Session not found or expired">
    The session ID does not exist on the edge — either it was never created, it was already consumed, or it expired. Session IDs have a 15-minute lifetime. If you see frequent 404s, check that the collector's `collect()` promise has resolved before you forward the ID to your backend.
  </Accordion>

  <Accordion title="429 — Rate limited">
    Your key has exceeded its request budget for the current window. Back off with exponential jitter and retry. If you consistently hit this limit, review your integration — calling the explain endpoint more than once per session ID is unnecessary.
  </Accordion>
</AccordionGroup>

<Note>
  Session IDs expire **15 minutes** after the collector issues them. Call the explain endpoint promptly — ideally within the same request that receives the session ID from the client.
</Note>


## Related topics

- [Heretic Quick Start: Collect Sessions and Read Verdicts](/quickstart.md)
- [GET /e Explain Endpoint: Full Request and Response Reference](/api/explain-endpoint.md)
- [Heretic API Keys: Collector Setup and Explain Endpoint](/dashboard/api-keys.md)
- [Heretic API: Base URL, Auth, and Error Codes Guide](/api/overview.md)
- [Heretic Verdict Schema: Complete JSON Field Reference](/api/verdict-schema.md)
