Agent Spending Policy Specification (ASPS)

Version: 1.1 Status: Stable Authors: LetAgentPay contributors Date: 2026-05-15

Abstract

ASPS v1 defines a vendor-neutral JSON format for describing spending rules for a single AI agent. It covers budgets, limits, velocity caps, categories, schedules, auto-approval, and account-level budget rules.

Changes in 1.1: added requests_per_minute and requests_per_hour velocity-cap fields, plus the corresponding velocity_limit standard check (Section 9). 1.1 is fully backward-compatible with 1.0 policies — both new fields are optional and an absent value preserves 1.0 evaluation behavior.

ASPS is not tied to a specific payment provider or AI framework. It works with Stripe, Visa, Google AP2, LangChain, CrewAI, OpenClaw, or any other system. LetAgentPay is the reference implementation.

For multi-agent coordination (shared budgets, phases, dependencies), see ASPS v2: Mission Policy.

1. Design Principles

  1. Vendor-neutral — no dependency on a specific payment provider or AI framework
  2. Declarative — policies describe what is allowed, not how to enforce it
  3. Composable — agent-level and account-level policies stack independently
  4. Human-readable — a non-engineer should be able to read a policy and understand the rules
  5. Machine-evaluable — any conforming engine can produce the same pass/fail result for a given request
  6. Incrementally adoptable — all fields are optional; an empty policy {} means "allow everything"

2. Terminology

TermDefinition
PrincipalThe entity that owns and funds agents (a person or organization)
AgentAn AI system authorized to make spending requests
RequestA single spending intent: amount, currency, category, description
PolicyA set of rules attached to an agent that govern its spending
Budget RuleAn account-level rule that governs aggregate spending across all agents
CheckA single pass/fail evaluation of one rule against a request
DecisionThe final outcome: approved, rejected, or pending (requires human review)

3. Policy Object

A Policy is a JSON object. All fields are optional. Omitted fields impose no restriction.

{
  "version": "1.1",
  "daily_limit": 500.00,
  "weekly_limit": 2000.00,
  "monthly_limit": 5000.00,
  "per_request_limit": 200.00,
  "requests_per_minute": 5,
  "requests_per_hour": 60,
  "allowed_categories": ["groceries", "food_delivery"],
  "blocked_categories": [],
  "schedule": { ... },
  "auto_approve": { ... },
  "metadata": {}
}

3.1 Fields

FieldTypeDescription
versionstringSpec version (e.g. "1.1"). Optional, defaults to latest
daily_limitnumberMaximum spending per calendar day
weekly_limitnumberMaximum spending per ISO week (Mon–Sun)
monthly_limitnumberMaximum spending per calendar month
per_request_limitnumberMaximum amount for a single request
requests_per_minuteintegerMaximum number of purchase requests in a single calendar minute. Caps frequency independently of amount — defends against runaway-loop spending of many small amounts
requests_per_hourintegerMaximum number of purchase requests in a single calendar hour. Same intent as requests_per_minute over a longer window
allowed_categoriesstring[]Whitelist of categories. If set, only these categories are allowed
blocked_categoriesstring[]Blacklist of categories. Ignored if allowed_categories is set
scheduleScheduleTime-based access rules
auto_approveAutoApproveConditions for automatic approval without human review
metadataobjectArbitrary key-value pairs for implementation-specific extensions

3.2 Amount semantics

  • All amounts are in the agent's configured currency (currency is not part of the policy — it's part of the agent/account configuration)
  • Amounts MUST be non-negative
  • Limit checks are inclusive: a $200 request against a $200 per_request_limit passes

3.2.1 Velocity semantics

  • requests_per_minute and requests_per_hour are non-negative integers
  • Limits are inclusive: requests_per_minute: 5 allows exactly 5 requests in a calendar minute; the 6th MUST fail
  • Windows are calendar-aligned (UTC or implementation-defined timezone), not sliding
  • A request counts against the velocity counter only after it passes all preceding checks and is recorded as approved, auto_approved, or pending. Requests rejected by earlier checks (status, category, etc.) MUST NOT count
  • Held (pending) requests count against velocity until they are released (rejected or expired). Approved → completed transitions do not increment the counter a second time

3.3 Category semantics

  • Each request carries exactly one category (a single string). If a purchase spans multiple domains, the agent or application selects the primary category
  • Categories are lowercase strings, underscore-separated (e.g. food_delivery)
  • Categories are user-defined — the specification does not mandate a fixed set. Each application chooses categories that fit its domain
  • The specification treats categories as opaque strings. A conforming engine does not validate whether a category value is "known" — it only checks membership in allowed_categories / blocked_categories

Evaluation rules:

  • If allowed_categories is set, it takes precedence over blocked_categories. A request with a category not in the list is rejected
  • If only blocked_categories is set, a request with a category in the list is rejected. All other categories are allowed
  • If neither list is set, all categories are allowed
  • A category not present in either list is implicitly allowed (unless allowed_categories is set, which acts as a whitelist)

Default categories (available for one-click import in LetAgentPay):

accommodation, clothing, education, electronics, entertainment,
flights, food_delivery, gas, groceries, health, household,
other, restaurants, subscriptions, taxi, transport

Categories are per-account. New accounts start with just other. Account owners can import the defaults above, create custom categories, and define aliases (synonyms) via the dashboard or API. If a purchase request uses an unknown category, it is resolved to other and flagged for review.

4. Schedule Object

Controls when the agent is allowed to spend.

{
  "timezone": "America/New_York",
  "default": {
    "allow": "09:00-17:00"
  },
  "overrides": [
    { "days": ["sat", "sun"], "deny": true },
    { "days": ["fri"], "allow": "09:00-13:00", "daily_limit": 100.00 }
  ]
}

4.1 Fields

FieldTypeDescription
timezonestringIANA timezone (e.g. America/New_York). Required if schedule is set
defaultobjectDefault time window. { "allow": "HH:MM-HH:MM" }
overridesScheduleOverride[]Day-specific overrides

4.2 ScheduleOverride

FieldTypeDescription
daysstring[]Day abbreviations: mon, tue, wed, thu, fri, sat, sun
allowstringTime window "HH:MM-HH:MM". Overrides default
denybooleanIf true, all spending is denied on these days
daily_limitnumberOverride daily limit for these days

4.3 Time window semantics

  • Format: "HH:MM-HH:MM" (24-hour)
  • Overnight windows are supported: "22:00-06:00" means 22:00 to next-day 06:00
  • Overrides take precedence over default
  • If an override matches the current day, the default is ignored
  • If deny: true is set, allow is ignored for that override

5. AutoApprove Object

Defines conditions under which a request that passes all checks is approved automatically (without human review).

{
  "enabled": true,
  "max_amount": 50.00,
  "categories": ["groceries", "food_delivery"]
}

5.1 Fields

FieldTypeDescription
enabledbooleanRequired. Whether auto-approval is active
max_amountnumberMaximum amount eligible for auto-approval
categoriesstring[]Only these categories are eligible. If omitted, all categories qualify

5.2 Evaluation

A request qualifies for auto-approval if ALL conditions are met:

  1. enabled is true
  2. amount <= max_amount (if max_amount is set)
  3. category is in categories (if categories is set)

If auto-approval does not qualify, the request enters pending state for human review.

6. Budget Rules (Account-Level)

Budget Rules apply across all agents under one principal. They are evaluated after agent-level policy checks pass.

{
  "name": "Weekday limit",
  "limit_type": "daily",
  "limit_amount": 200.00,
  "days_of_week": [0, 1, 2, 3, 4],
  "start_at": null,
  "end_at": null,
  "priority": 0,
  "is_active": true
}

6.1 Fields

FieldTypeDescription
namestringHuman-readable rule name
limit_typeenumdaily, weekly, monthly, or total
limit_amountnumberThe limit
days_of_weekint[] or nullDays when rule applies (0=Monday, 6=Sunday). null = all days
start_atdatetime or nullRule active from (for time-bound rules)
end_atdatetime or nullRule active until
priorityintConflict resolution: higher wins. Equal priority → strictest (min amount) wins
is_activebooleanWhether the rule is currently enforced

6.2 Evaluation order

  1. Filter active rules (is_active: true)
  2. Filter by time window (start_at / end_at)
  3. Filter by day of week
  4. Group by limit_type
  5. In each group, select the rule with the highest priority (tie-break: lowest limit_amount)
  6. Check each selected rule against aggregate spending

7. Request Object

A spending request that is evaluated against the policy.

{
  "amount": 42.50,
  "currency": "USD",
  "category": "groceries",
  "description": "Weekly grocery order from Instacart",
  "idempotency_key": "req_abc123"
}

7.1 Fields

FieldTypeRequiredDescription
amountnumberYesRequested amount (must be > 0)
currencystringYesISO 4217 currency code
categorystringYesSpending category
descriptionstringYesHuman-readable description of the purchase
idempotency_keystringNoClient-generated key to prevent duplicate requests

8. Check Result Object

Each rule evaluation produces a check result.

{
  "rule": "daily_limit",
  "result": "pass",
  "detail": "150.00/500.00 spent today"
}

8.1 Fields

FieldTypeDescription
rulestringRule identifier (e.g. status, velocity_limit, category, daily_limit, budget, account_budget:Rule Name)
resultenumpass or fail
detailstringHuman-readable explanation

9. Evaluation Order

A conforming policy engine MUST evaluate checks in this order:

  1. Agent status — agent must be active
  2. Velocity limit — projected request count vs. requests_per_minute / requests_per_hour
  3. Category — against allowed/blocked lists
  4. Per-request limit — single transaction cap
  5. Schedule — time window check
  6. Daily limit — against daily spending (including held amounts)
  7. Weekly limit — against weekly spending (including held amounts)
  8. Monthly limit — against monthly spending (including held amounts)
  9. Budget — against remaining budget (budget - spent - held)
  10. Account-level budget rules — aggregate checks across all agents

If any check fails, the request is rejected. Checks SHOULD be evaluated in full and reported back to the caller, with one exception:

Velocity short-circuit. When the velocity check fails, the engine MAY skip checks 3–10 and return immediately with at minimum the status and velocity_limit results. This is permitted because the explicit purpose of velocity caps is to absorb runaway-loop traffic — a runaway agent firing thousands of requests per minute would otherwise force the engine to perform every other check (including counter reads, schedule evaluation, and account-rule joins) on every rejected attempt. Implementations that prioritize completeness of the audit report over throughput MAY choose to evaluate all checks; both behaviors are conformant.

9.1 Suppression of repeated velocity rejections

A runaway agent can hit the velocity limit thousands of times within a single window. To preserve the audit trail without log spam, implementations MAY suppress duplicate rejection records within the same velocity window:

  • The first rejection in a window is persisted in full (audit record + check results).
  • Subsequent rejections in the same window MAY be suppressed from persistent storage. The engine MUST still return a valid response to the caller, and the response SHOULD reference the surviving audit record (e.g., reuse its request_id) so callers can fetch a coherent rejection reason.
  • Implementations SHOULD emit a real-time event (e.g., pub/sub) on each suppressed rejection so dashboards and alerting can observe the throttling.

This suppression behavior is permitted but not required. Implementations that record every rejection are also conformant.

10. Fund Holding

When a request enters pending state (awaiting human approval):

  • The requested amount MUST be reserved (held) against the agent's budget
  • Held amounts MUST be included in limit checks (daily, weekly, monthly, budget)
  • On approval: hold converts to spent
  • On rejection or expiry: hold is released

This prevents over-commitment when multiple requests are pending simultaneously.

11. Decision Flow

Request → Agent Policy Checks (1-9)
  ├── Velocity check fails → REJECTED (engine MAY short-circuit and suppress
  │                                      duplicate audit records within the window)
  ├── Any other check fails → REJECTED
  └── All checks pass → Account Budget Rules (10)
        ├── Any rule fails → REJECTED
        └── All rules pass → Auto-Approve check
              ├── Qualifies → APPROVED (auto)
              └── Does not qualify → PENDING (human review)

12. Extensibility

12.1 metadata field

The metadata field in the Policy object allows implementations to add custom fields without breaking compatibility. Conforming engines MUST ignore unknown metadata keys.

12.2 Categories

Categories are user-defined strings. There is no fixed set mandated by the specification. Implementations define whatever categories fit their domain.

12.3 Custom check rules

Implementations MAY add custom check rules. Custom rules MUST be evaluated after the standard 10 checks and MUST use a namespaced rule identifier (e.g. custom:geo_fence).

12.4 x402 Settlement Policy

When an agent uses the x402 protocol for on-chain micropayments, settlement-specific rules can be defined in an x402 extension object within the policy:

{
  "daily_limit": 100,
  "per_request_limit": 10,
  "x402": {
    "allowed_chains": ["base", "base-sepolia"],
    "blocked_domains": ["evil.com"],
    "allowed_domains": ["*.example.com", "api.coingecko.com"],
    "max_per_request": 1.00
  }
}
FieldTypeDescription
x402.allowed_chainsstring[]Chains the agent may pay on. Defaults to ["base", "base-sepolia"]
x402.blocked_domainsstring[]Domains the agent must not pay. Supports *.example.com wildcards
x402.allowed_domainsstring[]If set, only these domains are allowed. Supports wildcards
x402.max_per_requestnumberPer-request limit specific to x402 payments (USD). Overrides general per_request_limit for x402

The x402 object is evaluated in addition to the standard policy checks. General limits (daily_limit, monthly_limit, budget) apply to both fiat and x402 payments — amounts are converted to USD-equivalent for unified budget tracking.

Conforming engines that do not support x402 MUST ignore the x402 key.

13. JSON Schema

The normative JSON Schema for ASPS objects is published at: https://letagentpay.com/schemas/asps/v0.1/policy.json

The schema defines all objects: Policy (root), Schedule, ScheduleOverride, AutoApprove, Request, CheckResult, and BudgetRule.

Appendix A: Full Example

{
  "version": "1.1",
  "daily_limit": 500.00,
  "weekly_limit": 2000.00,
  "monthly_limit": 5000.00,
  "per_request_limit": 200.00,
  "requests_per_minute": 5,
  "requests_per_hour": 60,
  "allowed_categories": [
    "groceries",
    "food_delivery",
    "subscriptions",
    "transport"
  ],
  "schedule": {
    "timezone": "America/New_York",
    "default": {
      "allow": "08:00-22:00"
    },
    "overrides": [
      {
        "days": ["sat", "sun"],
        "allow": "10:00-18:00",
        "daily_limit": 100.00
      },
      {
        "days": ["wed"],
        "deny": true
      }
    ]
  },
  "auto_approve": {
    "enabled": true,
    "max_amount": 50.00,
    "categories": ["groceries", "food_delivery"]
  }
}

This policy says:

  • Agent can spend up to $500/day, $2000/week, $5000/month
  • Single purchase up to $200
  • No more than 5 requests per minute or 60 per hour (runaway-loop guard)
  • Only groceries, food delivery, subscriptions, and transport
  • Weekdays 08:00–22:00, weekends 10:00–18:00, no spending on Wednesdays
  • Weekend daily limit reduced to $100
  • Groceries and food delivery under $50 are auto-approved

Appendix B: Conformance

An implementation is ASPS-conformant if it:

  1. Accepts Policy objects as defined in Section 3
  2. Evaluates the 10 standard checks in the specified order (Section 9), with velocity short-circuit and suppression behaviors as permitted in Sections 9 and 9.1
  3. Produces Check Result objects as defined in Section 8
  4. Implements fund holding as defined in Section 10
  5. Ignores unknown fields in Policy objects (forward compatibility)

An implementation MAY extend the spec with custom checks, categories, and metadata, provided it does not alter the behavior of standard checks.

Backward compatibility (1.0 → 1.1)

A 1.0 policy is a valid 1.1 policy: both new velocity fields are optional, and an absent value means "no velocity cap" — identical to 1.0 behavior. A 1.0-only engine that consumes a 1.1 policy MUST ignore the unknown velocity fields per the forward-compatibility rule (point 5 above), which means it will under-enforce on policies that rely on velocity caps. Authors who depend on velocity enforcement SHOULD declare "version": "1.1" and verify the engine's reported version.