Policies
Policies are the heart of AutopayOS. They define what agents can and cannot do — spending limits, merchant restrictions, velocity controls, time windows, and more. Every intent is evaluated against the active policy before approval.
Overview
Agent Request → Policy Engine → Decision
- Approved — Request is within policy constraints
- Denied — Request violates one or more policy rules
Policy structure
A policy is a JSON document with these sections:
{
"version": "2.0",
"spend": {
"amount_cap": 100.00,
"currency": "USD",
"usage": "recurring",
"categories": ["groceries", "household", "electronics"]
},
"merchant": {
"allow_list": ["amazon.com", "walmart.com", "target.com"],
"deny_list": ["casino.com", "gambling.com"]
},
"vendor": {
"trust": "moderate"
},
"context": {
"presence": "optional",
"time_window": {
"days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
"hours": { "start": "09:00", "end": "18:00" },
"timezone": "America/New_York"
}
},
"risk": {
"velocity_limit": {
"max_transactions": 10,
"max_amount": 500.00,
"time_window": "24h"
},
"anomaly_flags": ["unusual_amount", "new_merchant", "rapid_succession"]
},
"rails": {
"allow": ["STRIPE", "VISA_IC"],
"priorities": ["STRIPE"],
"fail_over": true
}
}Spend controls
Control how much agents can spend.
Amount cap
Maximum amount per transaction:
{
"spend": {
"amount_cap": 100.00,
"currency": "USD"
}
}| Field | Type | Description |
|---|---|---|
amount_cap | number | Maximum amount per transaction |
currency | string | ISO 4217 currency code |
usage | string | single (one-time) or recurring |
Category restrictions
Limit purchases to specific merchant categories:
{
"spend": {
"categories": ["groceries", "household", "electronics"]
}
}Supported categories
| Category | MCC Codes | Description |
|---|---|---|
groceries | 5411, 5422 | Grocery stores, meat markets |
household | 5200, 5251 | Home improvement, hardware |
electronics | 5732, 5734 | Electronics stores |
restaurants | 5812, 5814 | Restaurants, fast food |
travel | 4111, 4511 | Airlines, hotels, car rental |
gas | 5541, 5542 | Gas stations |
healthcare | 5912, 8011 | Pharmacies, doctors |
entertainment | 7832, 7922 | Movies, theaters |
MCC code mapping
You can also use MCC codes directly:
{
"spend": {
"mcc_codes": ["5411", "5422", "5912"]
}
}Merchant controls
Define which merchants agents can transact with.
Allowlist
Only allow specific merchants:
{
"merchant": {
"allow_list": [
"amazon.com",
"walmart.com",
"*.target.com"
]
}
}Wildcards (*) are supported for subdomains.
Denylist
Block specific merchants:
{
"merchant": {
"deny_list": [
"casino.com",
"gambling.com",
"*.bet"
]
}
}Geographic restrictions
Restrict by merchant location:
{
"merchant": {
"geo": {
"allow_countries": ["US", "CA", "GB"],
"deny_countries": ["RU", "CN"]
}
}
}Velocity controls
Prevent excessive spending over time.
Transaction limits
{
"risk": {
"velocity_limit": {
"max_transactions": 10,
"time_window": "24h"
}
}
}Amount limits
{
"risk": {
"velocity_limit": {
"max_amount": 500.00,
"time_window": "24h"
}
}
}Combined limits
{
"risk": {
"velocity_limit": {
"max_transactions": 10,
"max_amount": 500.00,
"time_window": "24h"
}
}
}Time window options
| Window | Description |
|---|---|
1h | Per hour |
24h | Per day |
7d | Per week |
30d | Per month |
Time controls
Restrict when agents can transact.
Business hours
{
"context": {
"time_window": {
"hours": {
"start": "09:00",
"end": "18:00"
},
"timezone": "America/New_York"
}
}
}Day restrictions
{
"context": {
"time_window": {
"days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
"hours": { "start": "09:00", "end": "17:00" }
}
}
}Human Presence
Control when Human Presence verification is required.
Always require HP
{
"context": {
"presence": "required"
}
}HP threshold
{
"context": {
"presence": "optional",
"presence_threshold": 500.00
}
}Transactions over $500 require HP verification.
HP for new merchants
{
"context": {
"presence": "optional",
"presence_on_new_merchant": true
}
}Anomaly detection
Configure automatic anomaly detection.
{
"risk": {
"anomaly_flags": [
"unusual_amount",
"new_merchant",
"rapid_succession",
"unusual_time",
"geo_anomaly"
]
}
}| Flag | Trigger |
|---|---|
unusual_amount | Amount significantly higher than average |
new_merchant | First transaction with this merchant |
rapid_succession | Multiple transactions in quick succession |
unusual_time | Transaction outside normal hours |
geo_anomaly | Merchant location unexpected |
Payment rails
Configure which payment rails to use.
{
"rails": {
"allow": ["STRIPE", "VISA_IC"],
"priorities": ["STRIPE", "VISA_IC"],
"fail_over": true,
"fail_open": false
}
}| Field | Description |
|---|---|
allow | Which rails are allowed |
priorities | Preferred order |
fail_over | Try next rail on failure |
fail_open | Allow if all rails fail (dangerous) |
Trust levels
Configure vendor trust levels:
{
"vendor": {
"trust": "moderate"
}
}| Level | Description |
|---|---|
strict | Only verified merchants, require signatures |
moderate | Verified preferred, signatures optional |
permissive | Accept most merchants |
Create a policy
<tabs> <tab title="TypeScript">const policy = await client.createPolicy({
name: 'Shopping Agent Policy',
rules: {
version: '2.0',
spend: {
amount_cap: 100.00,
currency: 'USD',
categories: ['groceries', 'household']
},
merchant: {
allow_list: ['amazon.com', 'walmart.com']
},
risk: {
velocity_limit: {
max_transactions: 10,
time_window: '24h'
}
}
}
});
console.log('Policy created:', policy.id);curl https://api.autopayos.com/ap2/admin/policies \
-H "Authorization: Bearer $AUTOPAYOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Shopping Agent Policy",
"rules": {
"version": "2.0",
"spend": {
"amount_cap": 100.00,
"currency": "USD",
"categories": ["groceries", "household"]
},
"merchant": {
"allow_list": ["amazon.com", "walmart.com"]
}
}
}'Update a policy
await client.updatePolicy(policyId, {
rules: {
...existingRules,
spend: {
amount_cap: 200.00 // Increase limit
}
}
});Policy versioning
Policies are versioned. Each update creates a new version:
{
"id": "pol_abc123",
"version": 3,
"hash": "sha256:def456...",
"createdAt": "2025-12-17T10:00:00Z"
}When an intent is approved, it's bound to a specific policy version via the Policy Attestation:
{
"attestation": {
"policyId": "pol_abc123",
"policyVersion": 3,
"policyHash": "sha256:def456...",
"evaluatedAt": "2025-12-17T10:00:00Z"
}
}Link policy to agent
Assign a policy to a specific agent:
await client.updateAgent(agentDid, {
linkedPolicyId: policy.id
});Or use the default policy for all agents:
await client.setDefaultPolicy(policy.id);Policy evaluation
When an intent is created, the policy engine checks:
| Check | Policy Field | Reason Code |
|---|---|---|
| Amount | spend.amount_cap | AMOUNT_OVER_CAP |
| Currency | spend.currency | CURRENCY_NOT_ALLOWED |
| Categories | spend.categories | MCC_DENIED |
| Merchant allowlist | merchant.allow_list | MERCHANT_NOT_ALLOWED |
| Merchant denylist | merchant.deny_list | MERCHANT_DENIED |
| Velocity count | risk.velocity_limit.max_transactions | VELOCITY_EXCEEDED |
| Velocity amount | risk.velocity_limit.max_amount | SPENDING_LIMIT_EXCEEDED |
| Time window | context.time_window | TIME_WINDOW_VIOLATED |
| Geographic | merchant.geo | GEO_RESTRICTED |
| Anomaly | risk.anomaly_flags | ANOMALY_DETECTED |
Test a policy
Simulate policy evaluation without creating an intent:
const result = await client.simulatePolicy({
policyId: 'pol_abc123',
request: {
amount: 150.00,
currency: 'USD',
merchantDomain: 'casino.com'
}
});
console.log(result);
// {
// allowed: false,
// reasonCodes: ['AMOUNT_OVER_CAP', 'MERCHANT_DENIED']
// }Example policies
Conservative (low risk)
{
"spend": {
"amount_cap": 50.00,
"currency": "USD",
"categories": ["groceries"]
},
"merchant": {
"allow_list": ["walmart.com", "target.com"]
},
"context": {
"presence": "required"
},
"risk": {
"velocity_limit": {
"max_transactions": 3,
"max_amount": 100.00,
"time_window": "24h"
}
}
}Moderate (balanced)
{
"spend": {
"amount_cap": 200.00,
"currency": "USD",
"categories": ["groceries", "household", "electronics"]
},
"merchant": {
"deny_list": ["casino.com", "gambling.com"]
},
"context": {
"presence": "optional",
"presence_threshold": 100.00
},
"risk": {
"velocity_limit": {
"max_transactions": 10,
"max_amount": 500.00,
"time_window": "24h"
}
}
}Permissive (high trust)
{
"spend": {
"amount_cap": 1000.00,
"currency": "USD"
},
"merchant": {
"deny_list": ["casino.com"]
},
"context": {
"presence": "optional",
"presence_threshold": 500.00
},
"risk": {
"velocity_limit": {
"max_transactions": 50,
"time_window": "24h"
}
}
}Best practices
1. Start restrictive
Begin with conservative limits and loosen as needed:
// Start here
{ "spend": { "amount_cap": 50.00 } }
// Increase after trust is established
{ "spend": { "amount_cap": 100.00 } }2. Use allowlists for high-value
For sensitive operations, use allowlists instead of denylists:
// Safer: Only allow known merchants
{ "merchant": { "allow_list": ["amazon.com"] } }
// Riskier: Block known bad, allow unknown
{ "merchant": { "deny_list": ["casino.com"] } }3. Layer controls
Combine multiple controls for defense in depth:
{
"spend": { "amount_cap": 100.00 },
"merchant": { "allow_list": ["amazon.com"] },
"risk": { "velocity_limit": { "max_transactions": 5 } },
"context": { "presence_threshold": 50.00 }
}Next steps
- Intent Mandates — How policies are evaluated
- Evidence chain — Audit policy decisions
- Human Presence — Configure HP triggers