Agent Preflight

Decide before you spend a tool call.

Capability status

Available contracts: body validation and generic search/browse decisions. POST /v0.2/usage/dump is reserved and fails closed with HTTP 503 capability_unavailable before reading input or querying the ledger. Usage exports and repeat-price-discovery are not public capabilities. They require verified account ownership, settlement integration and end-to-end tests before release. Catalog entries are not evidence of operational paid endpoints. This release changes reuse to explicit permission and TTL, requires matching nonempty goal hashes, uses exact version-2 fingerprints, abstains for unsupported operations, and removes unsupported savings claims. Existing history must be repopulated from new responses.

Choose a capability

Body validation: POST /check-request (free), or MCP check_request. Send request + schema; receive valid, errors, warnings and requestHash. Tool-call decisions: POST /v0.2/preflight/tool-call ($0.002 USDC per settled paid POST), or MCP preflight_tool_call (free). Send a proposed generic search/browse call + budget; receive a decision. These are different input and output contracts.

What is sent, returned, and never forwarded

Preflight evaluates your supplied data; it never sends the proposed request to the destination provider, executes searches, fetches pages, or stores/retrieves a result cache. You execute the downstream tool and maintain history. A reuse response points to your own resultReference, not returned search content. Payment verification/settlement communicates with the facilitator separately. Do not include destination credentials, API keys, private keys or sensitive content. The service receives the submitted data; the Analytics Engine privacy statement is not a claim that data never reaches the service.

Supported tool payloads

Only search:web_search and browse:fetch are supported decision aliases, not native Tavily/Exa integrations. Search request: query string (1–500 characters), optional max_results integer (1–10), no other fields. Browse request: absolute http(s) url, no other fields. Unsupported operations return advise / advisory with UNSUPPORTED_OPERATION; this means unsupported here, not inherently invalid or wasteful at the provider. Provider-aware Tavily pricing and research are not shipped.

Inputs, defaults and budget convention

Required: nonempty runId and stepId; tool.provider, tool.operation, tool.request and nonnegative estimatedExternalCostUsd; budget.remainingUsd. All money is USD numbers. Optional estimatedModelCostUsd and reservedUsd default to 0; maxToolCallsRemaining defaults to no explicit call-count limit. History defaults to empty; retryCap defaults to 1; reuse requires an explicit positive freshnessTtlSeconds and canReusePriorResult=true; scope advice requires canReduceScope=true. Supply remainingUsd AFTER accounting for this consultation fee: the runtime does not subtract it. Affordability compares external + model estimates against remaining minus reserved. preflightPriceUsd changes the legacy model only, never the settled HTTP price. Supply explicit task scope and fresh caller-owned history; both the proposal and history must carry the same nonempty goalHash. These are caller assertions, not authenticated identities; never mix histories across customers. Unknown envelope fields are not a supported extension mechanism; the HTTP parser may ignore them. Schemas below describe the supported caller contract, not a promise of strict envelope rejection.

Search request

{
  "runId": "settlement-check-01",
  "stepId": "paid-call-01",
  "tool": {
    "provider": "search",
    "operation": "web_search",
    "request": {
      "query": "Acme Corp Series B funding",
      "max_results": 5
    },
    "estimatedExternalCostUsd": 0.01,
    "estimatedModelCostUsd": 0.002
  },
  "budget": {
    "remainingUsd": 0.08,
    "reservedUsd": 0.02,
    "maxToolCallsRemaining": 4
  },
  "task": {
    "goalHash": "settlement-verification"
  },
  "history": []
}

Matching successful response

{
  "decision": "allow",
  "enforcement": "execute",
  "reasonCodes": [
    "NO_PROVEN_WASTE"
  ],
  "requestFingerprint": "f2aa17128536fec8262229a5654e49e9abc18f7f7387cb571a0d035906da9e0f",
  "issues": [],
  "estimated": {
    "preflightCostUsd": 0,
    "priceChargedUsd": 0,
    "avoidableExternalCostUsd": 0,
    "avoidableModelCostUsd": 0,
    "netSavingsUsd": 0,
    "billable": false,
    "confidence": 0.5
  }
}

How to interpret the response

decision is allow, block, reuse, defer or advise. enforcement is execute, safe_to_skip or advisory; these are recommendations, not actions already performed. allow means no rule found waste, not guaranteed validity/success. block identifies invalid requests or terminal retry caps; inspect reasonCodes and issues. reuse recommends using your prior resultReference; matchedCallId identifies its history entry. defer reports budget infeasibility, not eliminated future cost. advise offers optional reduce_scope guidance without changing your payload. issues contain path/code/message. requestFingerprint is a SHA-256 identifier for the versioned exact proposal; record it after your actual tool call completes. estimated contains modeled fees and netSavingsUsd—not observed savings. Only reuse estimates avoided provider cost; block/defer do not claim eliminated provider spend, and model savings remain zero without measurement. confidence is a rule constant, not a calibrated probability. No response includes downstream content.

Browse request

{
  "runId": "settlement-check-01",
  "stepId": "paid-call-01",
  "tool": {
    "provider": "browse",
    "operation": "fetch",
    "request": {
      "url": "https://example.com/"
    },
    "estimatedExternalCostUsd": 0.008
  },
  "budget": {
    "remainingUsd": 0.08,
    "reservedUsd": 0.02,
    "maxToolCallsRemaining": 4
  },
  "task": {
    "goalHash": "settlement-verification"
  },
  "history": []
}

Reuse walkthrough

After the first allow, execute the destination tool yourself. Only if it succeeds, save its accessible result and the returned requestFingerprint; record toolCallId, outcome, ageSeconds and goalHash. On a later proposal, send that history entry (example below). The response recommends reuse_result with cache://caller-owned/result-1; your application retrieves it. Increment ageSeconds yourself. TTL comparison includes the exact boundary. Version-2 fingerprints preserve exact query case, whitespace, URL text and array order; only object key order is canonicalized. Old fingerprints no longer match; repopulate history from new responses. Do not assume semantic equivalence or freshness beyond these rules; disable reuse when freshness or result accessibility is uncertain. History is caller-supplied evidence, not server-verified state.

Second request with completed history

{
  "runId": "settlement-check-01",
  "stepId": "next-step",
  "tool": {
    "provider": "search",
    "operation": "web_search",
    "request": {
      "query": "Acme Corp Series B funding",
      "max_results": 5
    },
    "estimatedExternalCostUsd": 0.01,
    "estimatedModelCostUsd": 0.002
  },
  "budget": {
    "remainingUsd": 0.08,
    "reservedUsd": 0.02,
    "maxToolCallsRemaining": 4
  },
  "task": {
    "goalHash": "settlement-verification"
  },
  "history": [
    {
      "toolCallId": "paid-call-01",
      "fingerprint": "f2aa17128536fec8262229a5654e49e9abc18f7f7387cb571a0d035906da9e0f",
      "outcome": "success",
      "resultReference": "cache://caller-owned/result-1",
      "ageSeconds": 30,
      "goalHash": "settlement-verification"
    }
  ],
  "capabilities": {
    "canReusePriorResult": true
  },
  "freshnessTtlSeconds": 60
}

Free body-validation example

{
  "request": {
    "method": "POST",
    "url": "https://example.com/users",
    "body": {
      "name": "Alice"
    }
  },
  "schema": {
    "type": "object",
    "required": [
      "name"
    ],
    "properties": {
      "name": {
        "type": "string"
      }
    },
    "additionalProperties": false
  }
}

Try body validation without payment

curl -sS https://api.agent-preflight.com/check-request -H 'Content-Type: application/json' --data '{"request":{"method":"POST","url":"https://example.com/users","body":{"name":"Alice"}},"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}},"additionalProperties":false}}'

Validation scope and errors

The example returns valid=true, empty errors/warnings and a requestHash. Change name to a number to receive valid=false and a typed issue. Only request.body is checked against the supplied schema; this is not arbitrary API, OpenAPI or authentication validation. Supported schema keywords: type, required, properties, items, enum, const, minimum, maximum, minLength, maxLength, pattern, additionalProperties. Unsupported schema keywords are rejected by validation; do not assume full JSON Schema support. HTTP 200 means a check completed, not that valid=true or decision=allow. Malformed envelopes/JSON return 400; oversized HTTP bodies return 413. HTTP body limit: 65,536 bytes (read incrementally); tool inputs have a 16-level/10,000-node bound and at most 100 history entries. Nonnegative numeric context is capped at 1,000,000,000; schema limit: 32,768 bytes (oversized schema returns 400).

Formats and MCP connection

HTTP requests and responses use application/json. HTML /docs is for people; /llms.txt is an index; /llms-full.txt contains this complete guide; /openapi.json is OpenAPI 3.1, not an execution endpoint. Connect a Streamable HTTP MCP client to https://api.agent-preflight.com/mcp, initialize, then tools/list. Call check_request or preflight_tool_call with the same argument objects shown here. Use an MCP client: sending a bare tool body to /mcp is not a valid protocol call. MCP results expose structuredContent plus a text content block containing the serialized JSON. Both MCP tools are unpaid; model economics fields are still estimates, not invoices.

MCP tool-call example (after initialization)

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "preflight_tool_call",
    "arguments": {
      "runId": "settlement-check-01",
      "stepId": "paid-call-01",
      "tool": {
        "provider": "search",
        "operation": "web_search",
        "request": {
          "query": "Acme Corp Series B funding",
          "max_results": 5
        },
        "estimatedExternalCostUsd": 0.01,
        "estimatedModelCostUsd": 0.002
      },
      "budget": {
        "remainingUsd": 0.08,
        "reservedUsd": 0.02,
        "maxToolCallsRemaining": 4
      },
      "task": {
        "goalHash": "settlement-verification"
      },
      "history": []
    }
  }
}

Payment flow and receipts

The paid HTTP route uses x402 on Base mainnet (eip155:8453), $0.002 USDC per successfully settled consultation, including allow and advise. An unpaid 402 challenge is not a settled charge. Send Accept: application/json. An authorized x402-capable client reads PAYMENT-REQUIRED, checks network/asset/amount/recipient against its own spending policy, signs locally and retries with PAYMENT-SIGNATURE. Inspect the HTTP status, decision body and PAYMENT-RESPONSE settlement header; do not infer settlement from estimated.billable or estimated.priceChargedUsd. Those legacy model fields can be false/zero on a paid allow response. Never send wallet secrets to Preflight. A 503 signals payment configuration/facilitator failure, not a tool decision. Do not blindly replay paid requests after uncertain outcomes. No live transaction is necessary to read these docs or use the free interfaces.

When to use—or bypass—Preflight

Consider it when supplied history or budgets may reveal waste before a metered search/browse call. Bypass remote paid checks when a competent local validator/cache/budget check already solves the problem, or consultation overhead exceeds plausible benefit. Neither synthetic benchmarks nor estimated savings prove customer ROI. Costs, task quality and recovery work must be measured against a competent baseline. No safety, savings or downstream-success guarantee.

Tool-call input reference (supported contract)

{
  "type": "object",
  "required": [
    "runId",
    "stepId",
    "tool",
    "budget"
  ],
  "additionalProperties": false,
  "description": "Pre-execution tool-call decision input. This endpoint evaluates the proposed tool call and does not execute the external tool.",
  "properties": {
    "runId": {
      "type": "string",
      "minLength": 1,
      "description": "Caller-scoped run identifier"
    },
    "stepId": {
      "type": "string",
      "minLength": 1,
      "description": "Caller-scoped step identifier within the run"
    },
    "tool": {
      "type": "object",
      "required": [
        "provider",
        "operation",
        "request",
        "estimatedExternalCostUsd"
      ],
      "additionalProperties": false,
      "properties": {
        "provider": {
          "type": "string",
          "minLength": 1,
          "description": "Tool provider. Supported: search, browse"
        },
        "operation": {
          "type": "string",
          "minLength": 1,
          "description": "Tool operation. Supported: search:web_search, browse:fetch"
        },
        "request": {
          "type": "object",
          "description": "Provider-specific payload. search/web_search requires query (string 1..500) and optional max_results (1..10). browse/fetch requires url (http/https).",
          "additionalProperties": true,
          "properties": {
            "query": {
              "type": "string",
              "minLength": 1,
              "maxLength": 500
            },
            "max_results": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10
            },
            "url": {
              "type": "string",
              "minLength": 1
            }
          }
        },
        "estimatedExternalCostUsd": {
          "type": "number",
          "minimum": 0,
          "description": "Estimated USD cost of the external tool call"
        },
        "estimatedModelCostUsd": {
          "type": "number",
          "minimum": 0,
          "description": "Optional estimated USD model cost for this step"
        }
      }
    },
    "budget": {
      "type": "object",
      "required": [
        "remainingUsd"
      ],
      "additionalProperties": false,
      "properties": {
        "remainingUsd": {
          "type": "number",
          "minimum": 0
        },
        "reservedUsd": {
          "type": "number",
          "minimum": 0
        },
        "maxToolCallsRemaining": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "task": {
      "type": "object",
      "required": [
        "goalHash"
      ],
      "additionalProperties": false,
      "properties": {
        "goalHash": {
          "type": "string",
          "description": "Opaque goal correlation token"
        }
      }
    },
    "history": {
      "type": "array",
      "description": "At most 100 caller-owned prior outcomes; reuse requires explicit permission, positive freshness TTL and matching nonempty goal hashes.",
      "maxItems": 100,
      "items": {
        "type": "object",
        "required": [
          "toolCallId",
          "fingerprint",
          "outcome",
          "ageSeconds"
        ],
        "additionalProperties": false,
        "properties": {
          "toolCallId": {
            "type": "string"
          },
          "fingerprint": {
            "type": "string"
          },
          "outcome": {
            "type": "string",
            "enum": [
              "success",
              "terminal_failure",
              "transient_failure"
            ]
          },
          "resultReference": {
            "type": "string"
          },
          "ageSeconds": {
            "type": "number",
            "minimum": 0
          },
          "goalHash": {
            "type": "string"
          }
        }
      }
    },
    "capabilities": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "canReusePriorResult": {
          "type": "boolean"
        },
        "canReduceScope": {
          "type": "boolean"
        }
      }
    },
    "freshnessTtlSeconds": {
      "type": "number",
      "minimum": 0,
      "maximum": 1000000000,
      "description": "Explicit positive TTL required for reuse. Omitted or zero disables reuse."
    },
    "retryCap": {
      "type": "integer",
      "minimum": 1,
      "default": 1
    },
    "preflightPriceUsd": {
      "type": "number",
      "minimum": 0,
      "default": 0.002,
      "description": "Modeled actionable fee only; cannot change the actual HTTP settlement price."
    }
  }
}

Tool-call output reference

{
  "type": "object",
  "required": [
    "decision",
    "enforcement",
    "reasonCodes",
    "requestFingerprint",
    "issues",
    "estimated"
  ],
  "additionalProperties": false,
  "description": "Deterministic pre-execution decision. The destination tool is never executed by this service.",
  "properties": {
    "decision": {
      "type": "string",
      "enum": [
        "allow",
        "block",
        "reuse",
        "defer",
        "advise"
      ]
    },
    "enforcement": {
      "type": "string",
      "enum": [
        "execute",
        "safe_to_skip",
        "advisory"
      ]
    },
    "reasonCodes": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "requestFingerprint": {
      "type": "string",
      "pattern": "^[a-f0-9]{64}$"
    },
    "matchedCallId": {
      "type": "string"
    },
    "issues": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "path",
          "code",
          "message"
        ],
        "additionalProperties": false,
        "properties": {
          "path": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      }
    },
    "estimated": {
      "type": "object",
      "required": [
        "preflightCostUsd",
        "priceChargedUsd",
        "avoidableExternalCostUsd",
        "avoidableModelCostUsd",
        "netSavingsUsd",
        "billable",
        "confidence"
      ],
      "additionalProperties": false,
      "properties": {
        "preflightCostUsd": {
          "type": "number"
        },
        "priceChargedUsd": {
          "type": "number",
          "description": "Legacy modeled actionable fee, NOT an actual settlement receipt. Paid allow/advise responses can show zero here."
        },
        "avoidableExternalCostUsd": {
          "type": "number"
        },
        "avoidableModelCostUsd": {
          "type": "number"
        },
        "netSavingsUsd": {
          "type": "number"
        },
        "billable": {
          "type": "boolean"
        },
        "confidence": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "Rule constant, not a calibrated probability."
        }
      }
    },
    "recommendation": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "action": {
          "type": "string"
        },
        "resultReference": {
          "type": "string"
        }
      }
    }
  }
}