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.
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"
}
}
}'{
"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.
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_breach | A configured cap would be exceeded. Decisive unless the cap's action is flag. |
| amount_anomaly | At least 4× this agent's 30-day median. |
| amount_magnitude | Large 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_history | Fewer than 8 settled payments, so there is no baseline. Scored as mild uncertainty rather than as safe. |
| velocity_burst | More than 12 intents in 5 minutes. |
| retry_loop | 3+ identical merchant-and-amount intents in the burst window. |
| first_seen_merchant | This account has never paid this merchant. |
| merchant_denial_history | This account has denied this merchant before, often. |
| category_prior | The merchant category's inherent risk, weighted by recoverability. |
| rail_defect | An 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.
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.
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.
401a key was presented and did not verify, or stored-mode scoring was requested without one.402quota exhausted or a paid capability on a free plan.422the intent failed validation.429rate limited.503no ledger to score against.- A
500means no decision was produced. Treat it as unscored, never as an allow.