Autonomous Procurement Agent
Stop overpaying. Stop missing fraud. Every week, procurement teams lose money because a supplier's quote arrived as a messy email, a merged-cell SAP export, or a PDF scan with OCR artifacts. The unit price got misread. The line math was wrong. Nobody caught it — until the invoice was already paid.
Autonomous Procurement Agent handles every format. Every currency. With fraud detection that actually blocks, not just warns.
First-Use Initialisation
Before processing any quotes, configure your environment:
# Required in production — server refuses to start without this
export LS_WEBHOOK_SECRET="your_ls_webhook_secret"
# Optional: enables GPT-4o fallback for messy formats (plain-text, OCR scans).
# Without this, Engine 2 is skipped and only regex parsing runs.
# All parsing is LOCAL without this key.
export OPENAI_API_KEY="sk-..."
# Optional: override default ports and directories
export PARSER_DATA_DIR="./data" # cwd-anchored, never $HOME
export PROCU_WEBHOOK_PORT="3002"
# Start the webhook server (receives LS payment events → activates license)
node webhook-handler.js &
# → Listening on http://localhost:3002/webhook/lemon-squeezy
Never log raw quote content, vendor names, or API keys to stdout. The parser runs entirely locally unless OPENAI_API_KEY is set — in which case Privacy Shield scrubs all sensitive fields before any external call.
Quick Reference
| Situation | Action |
|---|---|
| Supplier quote in plain-text or email | Parse with Engine 1 (regex, <50ms) |
| Invoice has merged cells or OCR artifacts | Engine 2 triggers automatically (if OPENAI_API_KEY set) |
| F1 math error detected on a line item | Line blocked → whole PO escalated to REVIEW |
| F2 price spike >20% above historical avg | CRITICAL alert → auto-block |
| F3 duplicate PO within 7 days | Warning logged → duplicate flagged |
| Circuit breaker trips 2 consecutive approver failures | Safety-Freeze → all POs held for manual approval |
| Lemon Squeezy payment confirmed | Webhook writes to data/licenses.json automatically |
| High-value PO (>$50,000) needs LLM hint | generateLLMHint() called with USD-normalised structure |
| Receiving a quote without API key | Engine 1 regex only; no external calls made |
Installation
Via ClawHub (recommended)
clawhub install autonomous-procurement-agent
Manual
git clone https://github.com/arya-openclaw/autonomous-procurement-agent.git \
~/.openclaw/skills/autonomous-procurement-agent
cd ~/.openclaw/skills/autonomous-procurement-agent
npm install
Lemon Squeezy Webhook Setup
- Go to your Lemon Squeezy dashboard → Webhooks
- Add endpoint:
https://your-domain.com/webhook/lemon-squeezy - Copy the signing secret → set as
LS_WEBHOOK_SECRET - For local dev, use ngrok:
ngrok http 3002
No PayPal: Lemon Squeezy handles global tax (VAT/GST included in price) and supports Payoneer / World First / Wise payouts. PayPal is not supported due to high dispute fees and China-market account ban risk.
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
LS_WEBHOOK_SECRET |
Yes (prod) | — | HMAC-SHA256 signing secret from LS dashboard. Server refuses to start without it. |
OPENAI_API_KEY |
No | — | OpenAI key. Only needed for Engine 2 (GPT-4o fallback). Without it, all parsing is local. |
LS_PRO_VARIANT_ID |
No | 999 |
Lemon Squeezy variant ID for Pro tier |
LS_ENT_VARIANT_ID |
No | 2999 |
Lemon Squeezy variant ID for Enterprise tier |
PARSER_DATA_DIR |
No | ./data (cwd-anchored) |
Local directory for license DB + historical price baseline |
PROCU_WEBHOOK_PORT |
No | 3002 |
Webhook HTTP server port |
PROCU_ALLOWED_HOSTS |
No | localhost,127.0.0.1 |
Comma-separated allowed hosts for outbound license GET calls. Defaults to loopback only. Do not set to public IPs in production. |
OPENAI_MODEL |
No | gpt-4o |
OpenAI model for LLM fallback (only when OPENAI_API_KEY is set) |
Scenarios
1. Cross-Platform Quote Reconciliation
A manufacturing firm receives quotes from three suppliers: one as a CSV export, one as a plain-text email ("qty 8 × $2,800 = $22,400"), one as a scanned PDF forwarded from a WhatsApp photo. Procurement Agent normalises all three to a structured comparison table in under a second.
2. B2B Finance Real-Time Risk Audit
Finance receives a €47,000 PO from a long-term vendor. The line items all check out mathematically — but F2 flags that the unit price for the primary component is 34% above the 6-month average. The PO is auto-blocked before the CFO's signature is requested.
How F1 / F2 / F3 Work
F1 — Calculation Verification (Enterprise only)
Every line: unit_price × quantity ≠ line_total → line blocked, PO escalated.
Supplier quotes "8 units × $2,800 = $22,400". You calculate the same. F1 checks it. Supplier made a $200 arithmetic error in their favour. You catch it before signing.
F1 runs automatically on every parse. No configuration required.
F2 — Price Spike Detection (Enterprise only)
Current price > historical average × 1.20 → CRITICAL alert + auto-block.
Ball bearings purchased at $12/unit for 6 months. New quote: $16/unit. F2 flags this 34% spike before approval.
Baseline import (one-time):
node self-healing-parser.js import-baseline ./historical-prices.json
F3 — Duplicate Quote Detection (Enterprise only)
Same vendor + same total + within 7 days → duplicate warning.
Two RFQs sent. Supplier responds twice. Finance processes both. F3 catches the duplicate before you pay twice.
Architecture
Supplier quote (any format)
│
▼
┌─────────────────────┐
│ Engine 1: Regex │ ← JSON → HTML table → CSV → Plain text
│ (< 50ms) │
└──────────┬──────────┘
│
confidence < 0.5
or messy format
│
▼
┌─────────────────────┐
│ Engine 2: GPT-4o │ ← Only runs if OPENAI_API_KEY is set.
│ (opt-in LLM) │ All parsing is LOCAL without this key.
└──────────┬──────────┘
│
┌──────┴──────┐
│ Risk Engine │
│ F1 / F2 /F3 │ ← Enterprise only
└──────┬──────┘
│
risk_score > 0.5
│
▼
STATUS: REJECTED_FOR_REVIEW
Parsing Output Format
Every parseQuote() call returns a structured result. Here is the canonical schema:
{
"vendor_name": "Acme Corp",
"po_number": "PO-2024-0041",
"currency": "USD",
"line_items": [
{
"description": "Industrial Ball Bearing",
"quantity": 8,
"unit_price": 2800.00,
"line_total": 22400.00,
"extracted_raw": "8 × $2,800 = $22,400",
"f1_flag": false, // true if unit_price × qty ≠ line_total
"f2_flag": false, // true if >20% above historical avg
"anomaly": false,
"reason": null
}
],
"subtotal": 22000.00,
"tax": 1980.00,
"total": 23980.00,
"confidence_score": 0.93,
"parse_method": "regex", // "regex" | "llm_fallback" | "html_table" | "csv"
"is_llm_fallback": false,
"variant_detected": null, // "V4" | "V8" | "V10" | null
"anomaly_flags": [],
"fraud_flags": [],
"recommendation": "AUTO_APPROVED", // "AUTO_APPROVED" | "REVIEW" | "REJECT"
"safety_freeze": false,
"llm_error": null
}
Confidence Tiers
| Score | Tier | Meaning |
|---|---|---|
| ≥ 0.85 | high |
Regex pipeline succeeded cleanly |
| ≥ 0.5 | medium |
Partial parse; some fields recovered |
| < 0.5 | low |
Unparseable; falls back to LLM or returns error |
Privacy Shield (v1.0.0+)
Before any quote content is sent to the OpenAI API, it passes through a Privacy Shield — a local regex sanitiser that runs before the HTTP request is made. No external services are called; no data leaves your server at this stage.
| Field | Replacement | Example |
|---|---|---|
| Supplier/vendor name | [VENDOR_MASKED] |
"Acme Corp" → "[VENDOR_MASKED]" |
| Monetary amounts | [AMOUNT_MASKED] |
"$1,234.56" → "[AMOUNT_MASKED]" |
| Email / phone / fax | [PII_REDACTED] |
"john@corp.com" → "[PII_REDACTED]" |
| Street addresses | [PII_REDACTED] |
"12 Main St, Shenzhen" → "[PII_REDACTED]" |
Trigger: Privacy Shield is applied automatically whenever OPENAI_API_KEY is set and a quote requires GPT-4o fallback. It does not run in local-only regex mode.
GPT-4o receives enough structure to validate mathematical consistency and detect anomalies — but cannot see actual supplier prices or identities.
License Tiers
| Free | Pro ($9.99/mo) | Enterprise ($29.99/mo) | |
|---|---|---|---|
| Quotes/month | 20 | 500 | Unlimited |
| Parse formats | JSON, HTML, CSV | All formats | All formats + LLM fallback |
| F1 Calculation Check | — | — | ✅ Built in |
| F2 Price Spike Detection | — | — | ✅ Built in |
| F3 Duplicate Detection | — | — | ✅ Built in |
| CNY→USD Normalization | — | ✅ | ✅ |
| Approval flow | — | — | ✅ |
| Safety-Freeze circuit breaker | — | — | ✅ |
| Historical price baseline | — | — | ✅ |
| Priority support | — | — | ✅ |
Generic Setup (Other AI Agents)
For Claude Code, Codex, Copilot, or other agents:
mkdir -p ~/.openclaw/skills/autonomous-procurement-agent
git clone https://github.com/arya-openclaw/autonomous-procurement-agent.git \
~/.openclaw/skills/autonomous-procurement-agent
cd ~/.openclaw/skills/autonomous-procurement-agent
npm install
Parse a quote:
node self-healing-parser.js parse '<content>' [format] '{"email":"user@example.com"}'
Periodic Review
Review flagged POs regularly:
# List POs with active risk flags
grep -r "REJECTED_FOR_REVIEW\|CRITICAL\|suspicious" \
~/.procurement-agent-data/logs/ 2>/dev/null | tail -20
# Check Safety-Freeze status
grep "Safety-Freeze" ~/.procurement-agent-data/logs/*.log 2>/dev/null | tail -5
# Check license DB health
cat ~/.procurement-agent-data/data/licenses.json | python3 -m json.tool
Best Practices
- Always set
LS_WEBHOOK_SECRETin production — the server refuses to start without it. There is no bypass flag. OPENAI_API_KEYis opt-in — without it, no quote content is ever sent to any external API.- Import your historical price baseline before using F2 — without it, F2 spike detection uses a conservative built-in table.
- Keep
PARSER_DATA_DIRbacked up —data/licenses.jsonis the source of truth for all license state. - Do not commit
data/licenses.jsonto git — add it to.gitignore. License records are per-install, not per-repo. - Webhook logs are sanitised automatically —
sanitize()redacts email addresses and API keys before writing logs. - Review F2 spike alerts promptly — F2 is silent until a spike is detected; configure alerts accordingly.
Multi-Agent Support
OpenClaw (primary platform)
Activation: Automatic skill loading via ClawHub or workspace injection.
Detection: Trigger keywords (supplier quote, purchase order, vendor risk, price spike, fraud detection, approval escalation, safety freeze).
Claude Code / Codex
Activation: Direct invocation or workspace injection.
Setup: Copy skill to ~/.openclaw/skills/autonomous-procurement-agent, then:
export LS_WEBHOOK_SECRET="your_secret"
node webhook-handler.js &
Detection: Chat triggers listed above.
GitHub Copilot
Activation: Manual prompt — reference the skill directly in conversation.
Setup: Add to .github/copilot-instructions.md:
## Procurement Fraud Detection
When reviewing vendor quotes or purchase orders, use:
~/.openclaw/skills/autonomous-procurement-agent/self-healing-parser.js
Set OPENAI_API_KEY for LLM fallback on messy formats.
Agent-Agnostic Guidance
Regardless of agent, apply Autonomous Procurement Agent when:
- A supplier quote arrives in a non-standard format — plain-text, merged-cell spreadsheet, OCR scan.
- Multiple currencies need to be compared — CNY, USD, EUR reconciliation.
- A high-value PO needs validation — F1 catches math errors; F2 catches price spikes.
- Duplicate invoices are suspected — F3 detects same-vendor same-total within 7 days.
- Approval thresholds need to be enforced automatically — Safety-Freeze on circuit breaker trip.
Security Compliance (v1.0.3+)
D.1 — No Dynamic Execution
- Zero dynamic code execution — no eval, no new Function, no setTimeout with string argument, no vm.compile; all runtime code is static
- Zero dynamic
require()— all imports use static string literals at module load time - Zero dynamic
import()— no code generated from user input or config
D.2 — Production Bypass Mechanism Removed
- Any undocumented dev-bypass env vars have been completely removed for webhook signature validation — it has been completely removed from all code, manifest, and documentation as of v1.0.3
- If any bypass mechanism is detected at runtime the LLM call is aborted with a clear error
- Gate 0 in
callLLM()performs a hard abort if this env var is detected (kill switch, not a bypass)
D.3 — Three-Layer Privacy Shield
All LLM calls are routed through a mandatory three-gate interceptor in callLLM():
Layer 1 (upstream caller):
maskSensitiveData(content) — applied before prompt is built
↓
Layer 2 (callLLM Gate 1 — privacyShield):
Scans for [VENDOR_MASKED] and [AMOUNT_MASKED] tokens
between ---QUOTE TEXT START--- / ---QUOTE TEXT END--- markers.
Throws if neither token is found → raw content would go to wire.
↓
Layer 3 (callLLM Gate 2 — maskSensitiveData):
Defence-in-depth re-masking of the full prompt before transmission.
↓
Wire: only sanitized maskedPrompt is ever sent to api.openai.com
D.4 — Explicit Security Audit Logging
Every callLLM() invocation emits structured [SECURITY_AUDIT] logs:
[SECURITY_AUDIT] ✅ Payload sanitized. No PII detected in quote block.[SECURITY_AUDIT] vendor_masked=true | amount_masked=true | pii_redacted=true | safe_to_transmit=true[SECURITY_AUDIT] Gate-2 mask applied to full prompt.
If a masking token is missing: [SECURITY_AUDIT] ⚠️ ABORT — no mask token found...
D.5 — File System Isolation
- All license and history data is stored strictly within
process.cwd() + '/data'(configurable viaPARSER_DATA_DIR, which is anchored toprocess.cwd(), never$HOMEor/tmp) - Path traversal is prevented — no
../escaping the data directory - Data files written with
fs.fsyncSync()beforerenameSync()to avoid partial writes
D.6 — Webhook Signature Verification
LS_WEBHOOK_SECRETis mandatory — the server refuses to start without it- HMAC-SHA256 signature verification is performed on all inbound webhook payloads before any processing
- Failed signature verification returns HTTP 401, never a silent allow
D.7 — Manifest-to-Code Env Var Alignment
All 16 environment variables used in code are declared in manifest.json with explicit required: true/false flags. No undocumented runtime behaviour.
| Env Var | Required | Description |
|---|---|---|
LS_WEBHOOK_SECRET |
✅ | HMAC secret for webhook signature verification |
OPENAI_API_KEY |
❌ | OpenAI API key (LLM disabled without it) |
OPENAI_MODEL |
❌ | OpenAI model (default: gpt-4o-mini) |
PARSER_DATA_DIR |
❌ | ./data (cwd-anchored) |
PROCU_WEBHOOK_URL |
❌ | localhost:3002 |
PROCU_WEBHOOK_PORT |
❌ | 3002 |
PROCU_ALLOWED_HOSTS |
❌ | localhost,127.0.0.1 |
⚠️ SSRF protection:
PROCU_WEBHOOK_URLdefaults tolocalhost. OutboundhttpGetcalls are blocked to non-loopback hosts unlessPROCU_ALLOWED_HOSTSis explicitly set. Never point to private IPs outside your infrastructure. |LS_PRO_VARIANT_ID| ❌ | Lemon Squeezy Pro variant ID | |LS_ENT_VARIANT_ID| ❌ | Lemon Squeezy Enterprise variant ID | |LS_PRO_VARIANT_IDS| ❌ | Multi-price-point Pro IDs | |LS_ENTERPRISE_VARIANT_IDS| ❌ | Enterprise tier IDs | |LS_TEAM_VARIANT_IDS| ❌ | Team/Seat-based tier IDs |
D.8 — Dependency Hygiene
- No external dependencies — all secrets loaded from
process.envat runtime - Zero devDependencies
- No transitive dependencies with known CVE at time of release
