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

# QWED Tax integration for AI payroll and tax workflows

> Verify AI-generated payroll, tax, and withholding payloads with QWED Tax pre-flight middleware before forwarding them to Gusto, Avalara, or Stripe.

The primary usage patterns for QWED-Tax are the **TaxPreFlight** auditor for intent-based checks and the **QWEDTaxMiddleware** for intercepting AI-generated payroll payloads.

## Public imports

The entry points, diagnostic models, and every guard are re-exported from the top-level `qwed_tax` package, so most integrations can pull what they need from a single import:

```python theme={null}
from qwed_tax import (
    TaxPreFlight,
    TaxVerifier,
    QWEDTaxMiddleware,
    # Diagnostics
    TaxDiagnosticResult,
    TaxDiagnosticStatus,
    TaxAdvisoryCheck,
    # US guards
    ClassificationGuard,        # Federal IRS Common Law
    ABCClassificationGuard,     # State ABC Test (CA / NJ / MA)
    PayrollGuard,
    WithholdingGuard,
    ReciprocityGuard,
    Form1099Guard,
    # India guards
    GSTGuard,
    CryptoTaxGuard,
    InvestmentGuard,
    InterHeadAdjustmentGuard,
    DepositRateGuard,
    # Domain guards
    TDSGuard,
    InputCreditGuard,
    RemittanceGuard,
    DTAAGuard,
    TransferPricingGuard,
    PoEMGuard,
    NexusGuard,
    CapitalGainsGuard,
    SpeculationGuard,
    RelatedPartyGuard,
    ValuationGuard,
    AddressGuard,
)
```

The submodule paths under `qwed_tax.verifier`, `qwed_tax.guards.*`, `qwed_tax.jurisdictions.*`, `qwed_tax.middleware.*`, and `qwed_tax.diagnostics` continue to work; the top-level exports are the recommended surface.

<Note>
  `ClassificationGuard` at the top level is the federal IRS Common Law guard. The state-level ABC Test guard is exported as `ABCClassificationGuard` — it was renamed from `ClassificationGuard` to disambiguate the two. If you previously imported it from `qwed_tax.jurisdictions.us.classification_guard`, rename the class and switch to the top-level import: `from qwed_tax import ABCClassificationGuard`.
</Note>

## TaxPreFlight

The `TaxPreFlight` class routes a transaction intent to the guards required for its `action`. Every intent must declare a supported action and include a complete, verifiable claim for that action. `TaxPreFlight` **fails closed**: it blocks execution whenever the payload is missing required fields, contains non-numeric or non-finite values, or references an unsupported action.

<Note>
  `TaxPreFlight` is **fail-closed**. An intent that is empty, not a `dict`, uses an unsupported `action`, or does not contain a complete verifiable claim is **blocked by default** — it never silently passes through.
</Note>

```python theme={null}
from qwed_tax import TaxPreFlight

# 1. Initialize
preflight = TaxPreFlight()

# 2. Capture Intent (from AI Agent)
intent = {
    "action": "hire",
    "worker_type": "1099",  # LLM Decision
    "worker_facts": {
        "provides_tools": True,       # Fact: We gave them a laptop
        "reimburses_expenses": True,  # Fact: We pay for travel
        "indefinite_relationship": True,
    },
}

# 3. Audit
report = preflight.audit_transaction(intent)

# 4. Enforce
if not report["allowed"]:
    print(f"BLOCKED: {report['blocks']}")
    # Do NOT call Gusto/Stripe API
else:
    print(f"Checks run: {report['checks_run']}")
    print(f"Checks NOT run (known gaps): {report['checks_not_run']}")
    # Before calling Gusto/Stripe, decide whether the remaining gaps need
    # to be covered by additional guards or human review.
    # call_gusto_api()
```

### Supported actions

Every intent must include an `action` field. `TaxPreFlight` normalizes the action (trims whitespace, lowercases, and replaces spaces with underscores) before routing. If the action is missing, not a non-empty string, or not in the supported set, the intent is blocked with a report that lists every supported action.

| Action | Purpose | Required claim fields |
| - | - | - |
| `hire` | Worker classification (W-2 vs 1099) | `worker_type`, `worker_facts.provides_tools`, `worker_facts.reimburses_expenses`, `worker_facts.indefinite_relationship` |
| `economic_nexus` | US sales-tax nexus check | `state`, `sales_data.amount`, `sales_data.transactions`, `claimed_collects_tax` |
| `trade_tax` | Trader set-off or capital gains | `loss_head`, `offset_head`, `loss_amount` — **or** — `asset_type`, `dates.buy`, `dates.sell`, `claimed_rate` |
| `corporate_action` | Related-party loans or startup valuations | `lender_type`, `borrower_role`, `interest_rate`, `market_rate` — **or** — `investment_round="convertible_note"` plus `investment_amount`, `cap_price`, `discount`, `next_round_price` |
| `remit_money` | International remittance (LRS/TCS) | `remittance_amount_usd`, `purpose`, `fy_usage` |
| `expense_claim` | Input Tax Credit eligibility | `expense_category`, `amount`, `tax_paid` |
| `pay_invoice` | Withholding / TDS on vendor payment | `service_type`, `amount`, `ytd_payment` |

The following legacy action names are accepted and canonicalized automatically:

| Alias | Canonical action |
| - | - |
| `hire_worker` | `hire` |
| `worker_classification` | `hire` |
| `sales_tax_check` | `economic_nexus` |
| `sales_tax_assessment` | `economic_nexus` |

### Fail-closed routing

`audit_transaction` returns `allowed=False` whenever routing cannot produce a complete verifiable claim. The three blocking conditions are:

<Tabs>
  <Tab title="Empty or invalid payload">
    ```python theme={null}
    preflight.audit_transaction({})
    # {
    #   "allowed": False,
    #   "action": None,
    #   "blocks": ["TaxPreFlight requires a non-empty intent payload with an explicit action."],
    #   "checks_run": [],
    #   "checks_not_run": []
    # }
    ```
  </Tab>

  <Tab title="Unsupported action">
    ```python theme={null}
    preflight.audit_transaction({"action": "refund_customer"})
    # {
    #   "allowed": False,
    #   "action": "refund_customer",
    #   "blocks": [
    #     "TaxPreFlight requires a supported action. Supported actions: "
    #     "corporate_action, economic_nexus, expense_claim, hire, pay_invoice, "
    #     "remit_money, trade_tax."
    #   ],
    #   "checks_run": [],
    #   "checks_not_run": []
    # }
    ```
  </Tab>

  <Tab title="Incomplete claim">
    ```python theme={null}
    preflight.audit_transaction({"action": "trade_tax", "asset_type": "equity"})
    # {
    #   "allowed": False,
    #   "action": "trade_tax",
    #   "blocks": [
    #     "Action 'trade_tax' did not include a complete verifiable claim. "
    #     "Supported claim shapes: trader_setoff (loss_head, offset_head, loss_amount); "
    #     "capital_gains (asset_type, dates.buy, dates.sell, claimed_rate)."
    #   ],
    #   "checks_run": [],
    #   "checks_not_run": ["trader_setoff", "capital_gains"]
    # }
    ```
  </Tab>
</Tabs>

The report always echoes the canonical action (after alias resolution) in the `action` field and an empty `checks_run` list when the intent was rejected before any guard executed.

### Intent fields reference

`audit_transaction` returns a report with the following fields:

<ResponseField name="allowed" type="boolean">
  `true` only when every selected guard passes. Treat any `false` as a hard block.
</ResponseField>

<ResponseField name="action" type="string | null">
  The canonical action the intent resolved to (for example, `"hire"`). When the caller submits an unsupported action, this echoes back the raw value they sent; when the payload is entirely missing or empty, it is `null`.
</ResponseField>

<ResponseField name="blocks" type="string[]">
  One or more human-readable block reasons when `allowed` is `false`.
</ResponseField>

<ResponseField name="checks_run" type="string[]">
  Names of the guards that actually executed (for example, `["worker_classification"]`). Empty when the intent was rejected before any guard ran.
</ResponseField>

<ResponseField name="checks_not_run" type="string[]">
  Checks that were **not** executed for this action — either guards that weren't selected because their trigger fields were absent, or **known gaps** (checks not yet implemented for the action). For example, `action="hire"` reports `payroll_arithmetic`, `withholding_legality`, `reciprocity`, and `filing_obligations` as known gaps; `action="pay_invoice"` reports `itc_eligibility`, `gst_split`, and `rcm_applicability`. Use this list to decide which follow-up guards or human reviews are still required before execution.
</ResponseField>

<ResponseField name="advisories" type="string[]">
  Optional advisory messages emitted by guards (for example, required TDS deduction amounts).
</ResponseField>

### Redacting block reasons in untrusted contexts

`report["blocks"]` contains human-readable diagnostics that may echo back fields from the original intent — including worker facts, nexus sales figures, remittance purposes, or vendor service types. Treat these strings as **internal-only**. Log them to your audit trail. Do not render them verbatim in end-user surfaces, demo output, or any context where a downstream consumer could infer protected payroll or customer data.

In example and demo scripts bundled with `qwed-tax` (`examples/verify_tax_expansion.py`), outcomes are printed as high-level pass/fail only and verification details are intentionally redacted:

```python theme={null}
def _print_outcome(label: str, report: dict, allowed_text: str, blocked_text: str) -> None:
    """Print a safe high-level outcome without echoing raw block reasons."""
    print(f"{label}: {allowed_text if report['allowed'] else blocked_text}")
    if report["allowed"]:
        return

    print("   Verification details intentionally redacted.")
```

Mirror this pattern when surfacing preflight results outside a trusted audit boundary.

### Supported actions

Every intent must set `action` to one of the supported values below. Actions are normalized (trimmed, lowercased, spaces converted to underscores) before routing, and the listed aliases are accepted for backwards compatibility.

| Action | Aliases | Runs |
| - | - | - |
| `hire` | `hire_worker`, `worker_classification` | ClassificationGuard |
| `economic_nexus` | `sales_tax_check`, `sales_tax_assessment` | NexusGuard |
| `trade_tax` | — | SpeculationGuard, CapitalGainsGuard |
| `corporate_action` | — | RelatedPartyGuard, ValuationGuard |
| `remit_money` | — | RemittanceGuard |
| `expense_claim` | — | InputCreditGuard |
| `pay_invoice` | — | TDSGuard |

Any other `action` (including missing, empty, or non-string values) is blocked with a message listing the supported actions.

### Required fields per action

Each action requires a complete claim shape. If any listed field is missing, `null`, or empty, the transaction is blocked before guards run — no guard is invoked with partial inputs.

| Action | Required fields |
| - | - |
| `hire` | `worker_type`, `worker_facts.provides_tools`, `worker_facts.reimburses_expenses`, `worker_facts.indefinite_relationship` |
| `economic_nexus` | `state`, `sales_data.amount`, `sales_data.transactions`, `claimed_collects_tax` |
| `trade_tax` (trader set-off) | `loss_head`, `offset_head`, `loss_amount` |
| `trade_tax` (capital gains) | `asset_type`, `dates.buy`, `dates.sell`, `claimed_rate` |
| `corporate_action` (loans) | `lender_type`, `borrower_role`, `interest_rate`, `market_rate` |
| `corporate_action` (valuation) | `investment_round`, `investment_amount`, `cap_price`, `discount`, `next_round_price` |
| `remit_money` | `remittance_amount_usd`, `purpose`, `fy_usage` |
| `expense_claim` | `expense_category`, `amount`, `tax_paid` |
| `pay_invoice` | `service_type`, `amount`, `ytd_payment` |

<Note>
  For `trade_tax` and `corporate_action`, at least one of the claim shapes above must be fully present. Providing trigger fields for a claim but omitting any of its required fields is treated as an incomplete claim and fails closed. Startup valuation (`corporate_action`) additionally only runs when `investment_round == "convertible_note"`; other rounds are blocked with an explicit message.
</Note>

### Economic nexus requires an explicit boolean claim

<Warning>
  **Breaking schema change.** The `economic_nexus` action no longer accepts a free-form `tax_decision` string. Intents must include `claimed_collects_tax` as an explicit boolean. Free-form model text (e.g. `"No tax needed"`) is not interpreted as a verification claim. An intent that still sends `tax_decision` is missing `claimed_collects_tax`, so `audit_transaction` blocks it as an incomplete claim before `NexusGuard` runs.
</Warning>

Set `claimed_collects_tax` to `True` when the AI decided tax must be collected in the state, and `False` when it decided no collection is required. `NexusGuard` computes the state's threshold independently and verifies the boolean against the computed nexus.

```python theme={null}
# Before — free-form text (no longer interpreted; now blocked as incomplete)
intent = {
    "action": "economic_nexus",
    "state": "NY",
    "sales_data": {"amount": 600000, "transactions": 10},
    "tax_decision": "No tax needed",
}

# After — explicit boolean claim
intent = {
    "action": "economic_nexus",
    "state": "NY",
    "sales_data": {"amount": 600000, "transactions": 10},
    "claimed_collects_tax": False,
}

report = preflight.audit_transaction(intent)
# report["allowed"] = False
# report["blocks"] = [
#   "Nexus Violation: NY threshold exceeded (YTD Sales $600000 >= $500000). Tax collection is mandatory."
# ]
```

See [NexusGuard](/tax/guards#nexusguard-economic-nexus) for the underlying `check_nexus_liability` contract, including the computed-only result returned when the claim is omitted in direct guard calls.

### Numeric inputs must be finite

Guards that work with money — `RemittanceGuard` and `TDSGuard` — reject non-numeric, `NaN`, and infinite values. The surrounding `audit_transaction` call surfaces these as block reasons so an upstream LLM cannot smuggle through a malformed number.

```python theme={null}
report = preflight.audit_transaction({
    "action": "pay_invoice",
    "service_type": "PROFESSIONAL_FEES",
    "amount": float("inf"),
    "ytd_payment": 0,
})
# report["allowed"] = False
# report["blocks"] contains:
#   "TDS verification requires finite invoice_amount and ytd_payment values."
```

### Intent field types

`audit_transaction` inspects the following intent fields. Only include the fields relevant to your transaction. Guards are skipped when their required fields are absent.

| Field | Type | Used by |
| - | - | - |
| `worker_type` | `str` | ClassificationGuard |
| `worker_facts` | `dict` | ClassificationGuard |
| `state` | `str` | NexusGuard |
| `sales_data` | `dict` (`amount`, `transactions`) | NexusGuard |
| `claimed_collects_tax` | `bool` | NexusGuard |
| `loss_head` | `str` | SpeculationGuard |
| `loss_amount` | `float` | SpeculationGuard |
| `offset_head` | `str` | SpeculationGuard |
| `asset_type` | `str` | CapitalGainsGuard |
| `dates` | `dict` (`buy`, `sell`) | CapitalGainsGuard |
| `claimed_rate` | `str` | CapitalGainsGuard |
| `lender_type` | `str` | RelatedPartyGuard |
| `borrower_role` | `str` | RelatedPartyGuard |
| `interest_rate` | `float` | RelatedPartyGuard |
| `market_rate` | `float` | RelatedPartyGuard |
| `investment_round` | `str` | ValuationGuard (when `"convertible_note"`) |
| `investment_amount` | `str` | ValuationGuard |
| `cap_price` | `str` | ValuationGuard |
| `discount` | `str` | ValuationGuard |
| `next_round_price` | `str` | ValuationGuard |
| `remittance_amount_usd` | `float` | RemittanceGuard |
| `purpose` | `str` | RemittanceGuard |
| `fy_usage` | `float` | RemittanceGuard |
| `expense_category` | `str` | InputCreditGuard |
| `amount` | `float` | InputCreditGuard, TDSGuard |
| `tax_paid` | `float` | InputCreditGuard |
| `service_type` | `str` | TDSGuard |
| `ytd_payment` | `float` | TDSGuard |

### Capital gains date validation

For `action="trade_tax"`, the capital-gains route requires both `dates.buy` and `dates.sell` along with `asset_type` and `claimed_rate`. If any required field is missing, the transaction is blocked with a field-level diagnostic:

```python theme={null}
report = preflight.audit_transaction({
    "action": "trade_tax",
    "asset_type": "equity",
    "dates": {"buy": "2024-01-01"},  # sell missing
    "claimed_rate": "20"
})
# report["allowed"] = False
# report["blocks"] = [
#   "Action 'trade_tax' is missing required fields for capital_gains: dates.sell."
# ]
```

### TDS advisories now block

When `action="pay_invoice"` requires a TDS deduction, the transaction is **blocked** and the required deduction is surfaced in both `advisories` and `blocks`. The agent must re-issue the payment net of TDS before execution can proceed:

```python theme={null}
report = preflight.audit_transaction({
    "action": "pay_invoice",
    "service_type": "PROFESSIONAL_FEES",
    "amount": 50000,
    "ytd_payment": 0,
})
# report["allowed"] = False
# report["advisories"] = ["TDS Required: Deduct 5000.00 from payment."]
# report["blocks"] = [
#     "Invoice payment requires TDS deduction of 5000.00 before execution."
# ]
```

### Example: unsupported action

```python theme={null}
report = preflight.audit_transaction({"action": "do_everything"})
# report["allowed"] = False
# report["action"] = "do_everything"
# report["blocks"] = [
#     "TaxPreFlight requires a supported action. Supported actions: "
#     "corporate_action, economic_nexus, expense_claim, hire, "
#     "pay_invoice, remit_money, trade_tax."
# ]
```

## TaxVerifier

The `TaxVerifier` class provides jurisdiction-scoped access to guards. Initialize with `"US"` or `"INDIA"` to load the appropriate guard set.

```python theme={null}
from qwed_tax import TaxVerifier

# US jurisdiction
us_verifier = TaxVerifier(jurisdiction="US")
result = us_verifier.verify_us_payroll(entry=payroll_entry)

# India jurisdiction
india_verifier = TaxVerifier(jurisdiction="INDIA")
result = india_verifier.verify_india_crypto(
    losses={"VDA": Decimal("-5000")},
    gains={"BUSINESS": Decimal("10000")}
)

# Deposit rate verification (India)
result = india_verifier.verify_india_deposit(
    age=65,
    base_rate=Decimal("7.00"),
    claimed_rate=Decimal("7.50"),
    senior_premium=Decimal("0.50")
)
```

| Jurisdiction | Available methods |
| - | - |
| `US` | `verify_us_payroll(entry)` — runs PayrollGuard gross-to-net check |
| `INDIA` | `verify_india_crypto(losses, gains)` — runs CryptoTaxGuard set-off check |
| `INDIA` | `verify_india_deposit(**kwargs)` — runs DepositRateGuard FD rate check |

<Note>
  The US verifier also includes a `TaxPreFlight` instance accessible via `us_verifier.preflight` for intent-based auditing.
</Note>

## QWEDTaxMiddleware (Gusto interceptor)

The `QWEDTaxMiddleware` intercepts AI-generated payroll payloads before they reach execution APIs like Gusto. It validates the payload schema using Pydantic models and then runs deterministic gross-to-net verification.

<Note>
  **Unexpected fields are rejected.** The `PayrollEntry`, `TaxEntry`, `DeductionEntry`, `WorkerClassificationParams`, `ContractorPayment`, `WorkArrangement`, `Address`, and `VerificationResult` models are configured with `extra="forbid"`. Any payload with a key the model doesn't declare — including typos like `"net_pay_calimed"` or speculative additions like `"override_verification": true` — raises a `ValidationError` at the boundary, which the middleware surfaces as `status: "BLOCKED"` with `risk: "INVALID_PAYLOAD"`. Strip unknown fields from AI output before calling the middleware.
</Note>

```python theme={null}
from qwed_tax import QWEDTaxMiddleware

middleware = QWEDTaxMiddleware()

# Simulate an AI-generated payroll payload
ai_payload = {
    "payroll_entry": {
        "employee_id": "E001",
        "gross_pay": "5000.00",
        "taxes": [
            {"name": "Federal Income Tax", "amount": "800.00"},
            {"name": "Social Security", "amount": "310.00"}
        ],
        "deductions": [
            {"name": "401k", "amount": "250.00", "type": "PRE_TAX"}
        ],
        "net_pay_claimed": "3640.00"
    }
}

decision = middleware.process_ai_payroll_request(ai_payload)
```

### Response format

<Warning>
  Gross-to-net arithmetic alone is **not** full tax verification. When the middleware confirms the AI's math, it returns `status: "ARITHMETIC_VERIFIED"` with `execution_permitted: false` — execution remains blocked until classification, withholding legality, reciprocity, and filing obligations are also verified. There is no response shape that returns `execution_permitted: true` today.
</Warning>

<Tabs>
  <Tab title="Arithmetic verified (still blocked)">
    ```json theme={null}
    {
      "status": "ARITHMETIC_VERIFIED",
      "message": "Gross-to-net arithmetic verified. Legal/tax classification, withholding legality, reciprocity, and filing obligations were NOT checked. Execution blocked until full verification.",
      "execution_permitted": false,
      "checks_run": ["gross_to_net_arithmetic"],
      "checks_not_run": [
        "worker_classification",
        "withholding_legality",
        "reciprocity",
        "filing_obligations"
      ],
      "validated_payload": { ... }
    }
    ```
  </Tab>

  <Tab title="Blocked (hallucination)">
    ```json theme={null}
    {
      "status": "BLOCKED",
      "risk": "TAX_LOGIC_HALLUCINATION",
      "reason": "Mathematical discrepancy detected. Claimed Net: 3640.00, Calculated: 3500.00.",
      "execution_permitted": false
    }
    ```
  </Tab>

  <Tab title="Blocked (invalid payload)">
    ```json theme={null}
    {
      "status": "BLOCKED",
      "risk": "INVALID_PAYLOAD",
      "reason": "Missing or empty 'payroll_entry' in payload.",
      "execution_permitted": false
    }
    ```
  </Tab>
</Tabs>

<ResponseField name="status" type="string">
  `ARITHMETIC_VERIFIED` when the gross-to-net math passes, `BLOCKED` otherwise. There is no `VERIFIED` status — full verification requires legal checks that are not yet implemented in the middleware.
</ResponseField>

<ResponseField name="execution_permitted" type="boolean">
  Whether the payload is safe to forward to the execution API. Currently always `false`: the middleware fails closed until classification, withholding, reciprocity, and filing checks are wired in.
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable summary of what was verified and what was skipped (only present on `ARITHMETIC_VERIFIED`).
</ResponseField>

<ResponseField name="checks_run" type="string[]">
  Names of the checks the middleware actually executed (for example, `["gross_to_net_arithmetic"]`). Only present on `ARITHMETIC_VERIFIED`.
</ResponseField>

<ResponseField name="checks_not_run" type="string[]">
  Checks the middleware did **not** run for this payload — currently `worker_classification`, `withholding_legality`, `reciprocity`, and `filing_obligations`. Use this list to decide which guards to run separately (or which human reviews to require) before executing.
</ResponseField>

<ResponseField name="risk" type="string">
  Risk code when blocked: `TAX_LOGIC_HALLUCINATION`, `INVALID_PAYLOAD`, or `VERIFIER_ERROR`.
</ResponseField>

<ResponseField name="reason" type="string">
  Human-readable explanation of why the payload was blocked.
</ResponseField>

<ResponseField name="validated_payload" type="object">
  The validated payload (JSON-serialized) when the arithmetic check passes.
</ResponseField>

<Note>
  To move past `ARITHMETIC_VERIFIED`, call the remaining guards yourself: `ClassificationGuard` (worker type), `WithholdingGuard` (W-4 exempt legality), `ReciprocityGuard.verify_reciprocity()` (state withholding), and `Form1099Guard` (filing). Only forward the payload to Gusto/Avalara once every `checks_not_run` entry has an explicit pass.
</Note>

## Standalone guard usage

You can also use specific guards individually found in `qwed_tax.jurisdictions` and `qwed_tax.guards`.

```python theme={null}
from qwed_tax.jurisdictions.us.payroll_guard import PayrollGuard

pg = PayrollGuard()
result = pg.verify_fica_tax(gross_ytd=180000, current_gross=5000, claimed_ss_tax=310)
print(result.message)
# -> "FICA Error: Expected $68.20, Claimed $310. Limit logic failed? (Hit Limit this period...)"
```

## TypeScript SDK

Run compliance checks proactively in the browser or frontend.

```bash theme={null}
npm install @qwed-ai/tax
```

```typescript theme={null}
import { TaxPreFlight } from '@qwed-ai/tax';

const result = TaxPreFlight.audit({
  action: "hire",
  worker_type: "1099",
  worker_facts: {
    provides_tools: true,
    reimburses_expenses: true,
    indefinite_relationship: true,
  },
});

if (!result.allowed) {
   alert("Compliance Block: " + result.blocks.join(", "));
}
```

### Economic nexus in the TypeScript SDK

`TaxPreFlight.audit` runs `NexusGuard.checkNexus` whenever the intent contains a `sales_data` key. The nexus check enforces the same structured-claim contract as the Python guard.

**Structured claim contract.** Provide `claimed_collects_tax` as a boolean at the top level of the intent. `true` means the AI decided tax must be collected in the state, `false` means it decided no collection is required. The guard computes the state's threshold independently and verifies the boolean against the computed nexus. Non-boolean values (including strings like `"false"`) are rejected with the block reason `"Computed nexus liability only. Provide claimed_collects_tax as a boolean for deterministic verification."`.

**Legacy fallback.** When the intent does not contain a `claimed_collects_tax` key, `tax_decision: 'no_tax'` is interpreted as `claimed_collects_tax: false`. This fallback applies only when the structured claim is absent. If `claimed_collects_tax` is present but non-boolean, the intent is blocked even when `tax_decision: 'no_tax'` is also set. Any `tax_decision` value other than `'no_tax'` is not interpreted. Migrate to the structured boolean; the legacy string is a compatibility path.

```typescript theme={null}
import { TaxPreFlight } from '@qwed-ai/tax';

// Structured claim — over-threshold sale with a no-tax claim is blocked
const result = TaxPreFlight.audit({
  state: "NY",
  sales_data: { amount: 600000, transactions: 0 },
  claimed_collects_tax: false,
});
// result.allowed = false
// result.blocks = ["Nexus threshold exceeded in NY. Registration required."]

// Non-boolean claim — rejected even though it looks like a claim
TaxPreFlight.audit({
  state: "NY",
  sales_data: { amount: 600000, transactions: 0 },
  claimed_collects_tax: "false",
});
// blocks = ["Computed nexus liability only. Provide claimed_collects_tax as a boolean for deterministic verification."]

// Legacy fallback — only used when claimed_collects_tax is absent
TaxPreFlight.audit({
  state: "NY",
  sales_data: { amount: 600000, transactions: 0 },
  tax_decision: "no_tax",
});
// blocks = ["Nexus threshold exceeded in NY. Registration required."]
```

<Note>
  **Fails closed on unsupported states.** `NexusGuard.checkNexus` returns `{ verified: false, error: "State <CODE> not in configured nexus threshold table. Cannot verify nexus liability — block pending rule configuration." }` for any state that is not in the npm package's threshold table (currently NY, CA, TX, and FL). Unknown states were previously verified as no-nexus; they are now blocked. Treat this as a hold-and-escalate signal, not a "no tax owed" verdict.
</Note>

<Note>
  **Malformed sales facts are blocked, not skipped.** Nexus validation runs whenever the `sales_data` key is present, including falsy or explicitly `undefined` values. `sales_data` must be an object with numeric fields. Non-string `state` values, non-finite amounts (`NaN`, `Infinity`, non-numeric strings), and negative `amount` or `transactions` values all fail closed with a structured block reason instead of reading as "below threshold".
</Note>


## Related topics

- [QWED Tax guards for AI payroll and tax verification](/tax/guards.md)
- [QWED Tax: tax verification for AI agents](/tax/overview.md)
- [QWED: deterministic verification for LLMs and AI agents](/intro.md)
- [Changelog](/changelog.md)
- [QWED UCP guards for AI commerce verification](/ucp/guards.md)


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