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

# POST /v1/scan

> Inspect a payload and return a verdict, optionally with the personal data in it.

Inspects text and returns a decision, the rules that fired, and the offsets they fired at. It never returns a pattern.

## Request

<ParamField body="text" type="string" required>
  The payload, flattened to one string. An empty string is allowed.
</ParamField>

<ParamField body="block_severity" type={'"critical" | "high" | "medium" | "low"'} default="medium">
  Lowest severity that blocks. A match below it is still reported.
</ParamField>

<ParamField body="max_scan_chars" type="integer" default="100000">
  Payloads longer than this are truncated before scanning. Between 1 and 1,000,000.
</ParamField>

<ParamField body="include_evidence" type="boolean" default="false">
  Return the matched substring for each match. It is your own text.
</ParamField>

<ParamField body="tools" type="object[]">
  Tool declarations. Supplying them turns on the allowlist check.
</ParamField>

<ParamField body="tool_calls" type="object[]">
  Calls to validate: `{ id?, name, arguments }`, where `arguments` is the raw JSON string. Adds `tool_validation` to the verdict.
</ParamField>

<ParamField body="cloak" type="object">
  Ask for personal data detection in the same call. See below.
</ParamField>

<ParamField body="semantic" type={'"enforce" | "report" | "off"'} default="enforce">
  What the semantic pass may do. See [Detection](/guard/detection#tuning-the-semantic-pass).
</ParamField>

### The `cloak` block

<ParamField body="entities" type="string[]">
  Entity types to detect. Defaults to every type except `location` and `date`. See [Entity types](/cloak/entity-types).
</ParamField>

<ParamField body="min_confidence" type="number" default="0.5">
  Lowest detector score to accept, from 0 to 1.
</ParamField>

<ParamField body="skip_when_blocked" type="boolean" default="true">
  Skip detection when the static pass has already blocked the payload.
</ParamField>

```json theme={null}
{
  "text": "Please mail jane@example.com the invoice",
  "cloak": { "entities": ["email"] },
  "semantic": "report"
}
```

## Response

<ResponseField name="decision" type={'"allow" | "block"'}>
  The detection verdict.
</ResponseField>

<ResponseField name="blocked" type="boolean">
  Convenience mirror of `decision`.
</ResponseField>

<ResponseField name="reason" type="string | null">
  The rule id of the highest-severity match, or a classifier `task=label` pair when only the semantic pass fired.
</ResponseField>

<ResponseField name="severity" type="string | null">
  Highest severity across all matches.
</ResponseField>

<ResponseField name="categories" type="string[]">
  Distinct attack families that fired, in severity order.
</ResponseField>

<ResponseField name="matches" type="object[]">
  `{ signature_id, category, severity, start, end, evidence? }` for each match.
</ResponseField>

<ResponseField name="scanned_chars" type="integer">
  Characters scanned.
</ResponseField>

<ResponseField name="signature_version" type="string">
  Which rule set produced the verdict.
</ResponseField>

<ResponseField name="model_check" type="object">
  The semantic pass's answer. Present only when it was applicable. See [Detection](/guard/detection#the-two-stages).
</ResponseField>

<ResponseField name="tool_validation" type="object">
  Present only when `tool_calls` were submitted: `{ allowed, severity, calls[], model_check? }`.
</ResponseField>

<ResponseField name="cloak" type="object">
  Present only when you asked for detection.

  <Expandable title="properties">
    <ResponseField name="ran" type="boolean">Whether the detector answered. A payload it never saw is not one it cleared.</ResponseField>
    <ResponseField name="entities" type="object[]">`{ type, category, start, end, confidence }`. Never the value.</ResponseField>
    <ResponseField name="taxonomy_version" type="string">Identity of the type mapping.</ResponseField>
    <ResponseField name="latency_ms" type="number | null">Slowest detector call.</ResponseField>
    <ResponseField name="error" type="string | null">`disabled`, `timeout`, `unavailable`, `malformed_response`, `too_large`, or `skipped_blocked`.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="quota" type="object">
  Your plan and this month's usage.
</ResponseField>

```json theme={null}
{
  "decision": "block",
  "blocked": true,
  "reason": "FW-INJ-001",
  "severity": "critical",
  "categories": ["direct_injection"],
  "matches": [
    {
      "signature_id": "FW-INJ-001",
      "category": "direct_injection",
      "severity": "critical",
      "start": 0,
      "end": 32
    }
  ],
  "scanned_chars": 32,
  "signature_version": "2026.09.16",
  "quota": {
    "plan": "free",
    "period": "2026-09",
    "aho": {
      "scans": { "used": 412, "limit": null, "remaining": null },
      "blocks": { "used": 7, "limit": null, "remaining": null }
    },
    "ai": {
      "scans": { "used": 96, "limit": 100000, "remaining": 99904 },
      "blocks": { "used": 3, "limit": 5000, "remaining": 4997 }
    },
    "overflow": 0,
    "enforcement_withheld": false
  }
}
```

## Notes

* If detection does not answer, the scan still returns the attack verdict, with `cloak.ran: false`. What that means for the request is up to the caller. The SDKs refuse by default.
* The verdict is not cached. It is a function of the payload and the live rule set.
* A rejected tool call blocks the payload. The tool verdict is folded into the payload verdict so a caller that reads `blocked` cannot forward a request whose call was rejected.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.