Spend7

API reference

One endpoint does the work. The rest configure it, read what it decided, and turn a decision into evidence.

Full machine-readable spec: OpenAPI 3.1 · llms.txt

Authentication

Send an API key from the dashboard as Authorization: Bearer sp7_live_…, or in X-API-Key. A key that does not verify returns 401 rather than silently dropping you to the free tier: a typo should not look like a clean check.

POST /api/v1/risk-check

Call it before executing a payment. Amounts are in the smallest currency unit: 1250 is $12.50 in USD.

bash
curl -s https://spend7.com/api/v1/risk-check \
  -H 'Authorization: Bearer sp7_live_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "intent": {
      "agentId": "research-agent-01",
      "amountMinor": 4200,
      "currency": "USD",
      "merchantId": "api.example-data.com",
      "merchantName": "Example Data API",
      "category": "data_api",
      "rail": "x402",
      "reference": "job-8813",
      "x402": {
        "maxAmountRequiredMinor": 5000,
        "authorizedAmountMinor": 4200,
        "network": "base",
        "asset": "USDC",
        "nonce": "0x9f3c…",
        "maxTimeoutSeconds": 60,
        "quoteAgeSeconds": 4,
        "scheme": "exact"
      }
    }
  }'
json
{
  "decision": "flag",
  "score": 54,
  "signals": [
    {
      "id": "amount_anomaly",
      "label": "Amount is far above this agent's normal",
      "points": 26,
      "detail": "$420.00 is 9.8× the median $42.80 across 31 payments by research-agent-01 in the last 30 days."
    },
    {
      "id": "first_seen_merchant",
      "label": "First payment to this merchant",
      "points": 12,
      "detail": "This account has never paid api.example-data.com before…"
    },
    { "id": "policy_cap_approaching", "label": "Approaching a spend cap", "points": 11, "detail": "…" },
    { "id": "category_prior", "label": "Category prior: Data and content APIs", "points": 3, "detail": "…" }
  ],
  "caps": [
    {
      "limit": { "scope": "agent", "agentId": "research-agent-01", "window": "daily", "capMinor": 50000, "currency": "USD" },
      "usedMinor": 3180, "wouldBeMinor": 45180, "capMinor": 50000,
      "breached": false, "fractionUsed": 0.9036
    }
  ],
  "summary": "Flagged at 54/100. $420.00, research-agent-01 → Example Data API, on x402. …",
  "recommendedAction": "Step up before executing: put a human in the loop, …",
  "graphScoring": { "ran": false, "reason": "Cross-agent … graph scoring is a paid-plan capability. …" },
  "contextSource": "ledger",
  "paymentId": "6c1f0a3e-…",
  "ruleset": { "version": "2026.08.1", "hash": "…" },
  "evaluatedAt": "2026-08-08T11:04:22.118Z"
}

The decision

  • allow: execute.
  • flag: score at or above 50. Step up to a human or an explicit confirmation. It is not a soft allow, and treating it as one is the most common way to misuse this API.
  • deny: score at or above 80, or a decisive signal fired. A decisive signal (a hard cap breach, a blocked merchant, a confirmed bad counterparty, a broken protocol invariant) denies regardless of the total.

Stateless mode

Send a context object and Spend7 scores against the ledger you supply, reading nothing stored and writing nothing. Useful for testing a policy before adopting it, and for callers who keep their own transaction store. It works without an account, capped at 25 checks a rolling day unauthenticated.

bash
curl -s https://spend7.com/api/v1/risk-check \
  -H 'Content-Type: application/json' \
  -d '{
    "intent": {
      "agentId": "shopping-agent", "amountMinor": 90000, "currency": "USD",
      "merchantId": "giftcards.example", "category": "gift_cards", "rail": "other"
    },
    "context": {
      "limits": [
        { "scope": "agent", "agentId": "shopping-agent", "window": "daily",
          "capMinor": 50000, "currency": "USD" }
      ],
      "history": [
        { "agentId": "shopping-agent", "merchantId": "shop.example.com", "amountMinor": 2400,
          "currency": "USD", "decision": "allow", "createdAt": "2026-08-08T09:00:00Z" }
      ]
    }
  }'

Every response carries contextSource, so you can always tell which ledger a decision came from. When neither a stored ledger nor an inline context is available you get a 503, not a score against nothing.

What the signals mean

policy_cap_breachA configured cap would be exceeded. Decisive unless the cap's action is flag.
amount_anomalyAt least 4× this agent's 30-day median.
amount_magnitudeLarge in absolute terms, independent of any baseline: ramped from 500 whole currency units and saturating at 50,000. Damped once the agent's own baseline is already that size.
no_historyFewer than 8 settled payments, so there is no baseline. Scored as mild uncertainty rather than as safe.
velocity_burstMore than 12 intents in 5 minutes.
retry_loop3+ identical merchant-and-amount intents in the burst window.
first_seen_merchantThis account has never paid this merchant.
merchant_denial_historyThis account has denied this merchant before, often.
category_priorThe merchant category's inherent risk, weighted by recoverability.
rail_defectAn x402 or AP2 protocol check failed. Usually decisive.
graph_*Cross-account signals: merchant fan-in, platform denial rate, agent fan-out, coordinated bursts, confirmed bad counterparty. Paid plans.

Spend limits

GET reads the policy; PUT replaces it wholesale in one transaction. Replace rather than patch, because a caps policy that can be half-applied is one nobody can reason about.

bash
curl -s -X PUT https://spend7.com/api/v1/spend-limits \
  -H 'Authorization: Bearer sp7_live_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "limits": [
      { "scope": "agent", "agentId": "research-agent-01", "window": "daily",
        "capMinor": 25000, "currency": "USD" },
      { "scope": "global", "window": "per_transaction", "capMinor": 10000, "currency": "USD" },
      { "scope": "category", "category": "cloud_infrastructure", "window": "monthly",
        "capMinor": 500000, "currency": "USD", "action": "flag" }
    ],
    "lists": { "blockedCategories": ["gambling", "gift_cards"] }
  }'

Transaction log

GET /api/v1/transactions, filterable by agentId, merchantId, decision and rail. Read-only: there is no endpoint that edits or deletes a decision, because an audit trail you can rewrite is not an audit trail.

Chargeback-assist claims

Raise a claim against a stored paymentId and get the evidence packet built from what was recorded at the time. Paid capability. Spend7 does not underwrite the loss.

bash
curl -s https://spend7.com/api/v1/claims \
  -H 'Authorization: Bearer sp7_live_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "paymentId": "6c1f0a3e-…",
    "reason": "unauthorized",
    "lossMinor": 90000,
    "narrative": "The agent key was rotated after a leak; this payment postdates the rotation."
  }'

# then, the packet as a PDF you can attach to a dispute
curl -s -o claim.pdf https://spend7.com/api/v1/claims/{claimId}/evidence \
  -H 'Authorization: Bearer sp7_live_…'

Limits and errors

  • Free plan: 50 checks per rolling day against stored history.
  • Unauthenticated stateless: 25 per rolling day per IP.
  • 401 a key was presented and did not verify, or stored-mode scoring was requested without one. 402 quota exhausted or a paid capability on a free plan. 422 the intent failed validation. 429 rate limited. 503 no ledger to score against.
  • A 500 means no decision was produced. Treat it as unscored, never as an allow.
MCP server →x402 fields →AP2 fields →Framework integrations →