Refunds v1 Policy API Documentation

Overview

The Refunds v1 policy provides comprehensive protection for refund endpoints with payment capabilities, assurance levels, and transaction limits. This policy enforces strict validation rules to prevent fraud and ensure financial security.

Policy Features

āœ… Core Features

  • Multi-Currency Support: Direct currency-to-currency validation (no conversion)
  • Atomic Daily Caps: Race-safe daily spending limits per currency
  • Idempotency Protection: Prevents duplicate refunds with 24-hour deduplication
  • Assurance Tiers: Amount-based assurance level requirements
  • Cross-Currency Denial: Explicitly denies cross-currency refunds in v1
  • Order Balance Validation: Prevents over-refunding orders
  • Region Validation: Geographic restrictions
  • Reason Code Validation: Enforced refund reason codes

šŸ”’ Security Features

  • Amount Precision Validation: Currency-specific decimal place enforcement
  • Bounds Checking: Prevents extreme amounts and negative values
  • Rate Limiting: Per-IP rate limiting to prevent DoS attacks
  • Verifiable Attestation: Comprehensive Verifiable Attestation for all decisions
  • Cryptographic Signing: Decision receipts with HMAC verification

API Endpoints

Policy Verification

Endpoint: POST /api/verify/policy/finance.payment.refund.v1

Description: Verify refund request against finance.payment.refund.v1 policy

Headers:

Content-Type: application/json
X-Agent-Passport-Id: agents/your_agent_id

Request Body:

{
  "agent_id": "agents/your_agent_id",
  "context": {
    "order_id": "ORD-12345",
    "customer_id": "CUST-67890",
    "amount_minor": 5000,
    "currency": "USD",
    "region": "US",
    "reason_code": "customer_request",
    "idempotency_key": "unique_key_123",
    "order_currency": "USD",
    "order_total_minor": 10000,
    "already_refunded_minor": 2000,
    "note": "Customer requested refund",
    "merchant_case_id": "CASE-789"
  }
}

Required Fields:

  • order_id: Order identifier
  • customer_id: Customer identifier
  • amount_minor: Refund amount in minor units (cents, yen, etc.)
  • currency: Currency code (USD, EUR, GBP, JPY, etc.)
  • region: Geographic region (US, EU, APAC, etc.)
  • reason_code: Refund reason code
  • idempotency_key: Unique key for deduplication

Optional Fields:

  • order_currency: Original order currency (for cross-currency validation)
  • order_total_minor: Original order total in minor units
  • already_refunded_minor: Previously refunded amount
  • note: Additional notes
  • merchant_case_id: Merchant's case identifier

Success Response (200):

{
  "allow": true,
  "decision_id": "dec_01HJ...7",
  "expires_in": 60,
  "remaining_daily_cap": {
    "USD": 25000
  },
  "passport": {
    "agent_id": "agents/your_agent_id",
    "status": "active",
    "assurance_level": "L2",
    "capabilities": ["finance.payment.refund"],
    "limits": {
      "supported_currencies": ["USD", "EUR", "GBP"],
      "currency_limits": {
        "USD": {
          "max_per_tx": 10000,
          "daily_cap": 50000
        }
      },
      "refund_reason_codes": ["customer_request", "defective", "not_as_described"]
    },
    "regions": ["US", "EU"]
  }
}

Error Response (403):

{
  "allow": false,
  "reasons": [
    {
      "code": "daily_cap_exceeded",
      "message": "Daily cap 50000 USD exceeded for USD; current 48000 + 5000 > 50000"
    }
  ],
  "decision_id": "dec_01HJ...8",
  "remaining_daily_cap": {
    "USD": 2000
  }
}

Error Codes

Code Description HTTP Status
missing_required_field Required field is missing 400
invalid_amount Amount validation failed 400
currency_not_supported Currency not supported 400
invalid_idempotency_key Invalid idempotency key format 400
amount_exceeds_per_tx Amount exceeds per-transaction limit 403
daily_cap_exceeded Daily spending cap exceeded 403
assurance_too_low Insufficient assurance level 403
region_not_allowed Region not allowed for agent 403
reason_code_invalid Invalid reason code 403
idempotency_replay Duplicate idempotency key 403
order_balance_exceeded Refund exceeds order balance 403
cross_currency_denied Cross-currency refunds not supported 403

Assurance Level Requirements

Amount Range Required Assurance Description
≤ $100 USD L2 GitHub organization verification
$100 - $500 USD L3 Domain .well-known or TXT record
> $500 USD L4 Human handoff required (denied in v1)

Note: Amounts are converted to USD equivalent for assurance calculation

Supported Currencies

Currency Code Decimals Minor Unit
US Dollar USD 2 Cent
Euro EUR 2 Cent
British Pound GBP 2 Penny
Japanese Yen JPY 0 Yen
Canadian Dollar CAD 2 Cent
Australian Dollar AUD 2 Cent
Swiss Franc CHF 2 Rappen
Chinese Yuan CNY 2 Fen
Indian Rupee INR 2 Paisa
Brazilian Real BRL 2 Centavo
Mexican Peso MXN 2 Centavo

Reason Codes

Code Description
customer_request Customer requested refund
defective Product was defective
not_as_described Product not as described
duplicate Duplicate charge
fraud Fraudulent transaction
cancelled Order was cancelled
returned Product was returned

SDK Usage

Node.js SDK

const { verifyPolicy } = require('@aporthq/sdk-node');

// Basic usage
const result = await verifyPolicy('agents/your_agent_id', 'finance.payment.refund.v1', {
  order_id: 'ORD-12345',
  customer_id: 'CUST-67890',
  amount_minor: 5000,
  currency: 'USD',
  region: 'US',
  reason_code: 'customer_request',
  idempotency_key: 'unique_key_123'
});

if (result.allowed) {
  console.log('Refund approved:', result.result);
} else {
  console.error('Refund denied:', result.error);
}

Python SDK

from aporthq_sdk_python import check_policy_compliance

# Basic usage
result = await check_policy_compliance(
    agent_id="agents/your_agent_id",
    policy_id="finance.payment.refund.v1",
    context={
        "order_id": "ORD-12345",
        "customer_id": "CUST-67890",
        "amount_minor": 5000,
        "currency": "USD",
        "region": "US",
        "reason_code": "customer_request",
        "idempotency_key": "unique_key_123"
    }
)

if result["allowed"]:
    print("Refund approved:", result["policy_result"])
else:
    print("Refund denied:", result["error"])

Middleware Usage

Express.js Middleware

const express = require('express');
const { requireRefundsPolicy } = require('@aporthq/middleware-express');

const app = express();
app.use(express.json());

// Apply refunds policy to route
app.post('/refund', requireRefundsPolicy('agents/your_agent_id'), (req, res) => {
  // Policy already verified, process refund
  const policyResult = req.policyResult;
  
  res.json({
    success: true,
    refund_id: 'ref_123',
    decision_id: policyResult.evaluation.decision_id,
    remaining_daily_cap: policyResult.evaluation.remaining_daily_cap
  });
});

FastAPI Middleware

from fastapi import FastAPI, Depends
from aporthq_middleware_fastapi import require_refunds_policy

app = FastAPI()

@app.post("/refund")
async def process_refund(
    policy_result = Depends(require_refunds_policy("agents/your_agent_id"))
):
    # Policy already verified, process refund
    return {
        "success": True,
        "refund_id": "ref_123",
        "decision_id": policy_result.evaluation.decision_id,
        "remaining_daily_cap": policy_result.evaluation.remaining_daily_cap
    }

Monitoring and Audit

Verifiable Attestation

All policy decisions are logged with the following information:

  • Decision ID
  • Agent ID
  • Timestamp
  • Outcome (allow/deny)
  • Reasons for denial
  • Context data
  • Cryptographic signature

Monitoring Endpoints

Get Verifiable Attestation: GET /api/owners/{owner_id}/audit
Get Alerts: GET /api/monitoring/alerts
Get Metrics: GET /api/metrics (admin only)

Example Verifiable Attestation Entry

{
  "decision_id": "dec_01HJ...7",
  "agent_id": "agents/your_agent_id",
  "timestamp": "2024-01-16T10:30:00Z",
  "outcome": "deny",
  "reasons": [
    {
      "code": "daily_cap_exceeded",
      "message": "Daily cap 50000 USD exceeded"
    }
  ],
  "context": {
    "order_id": "ORD-12345",
    "amount_minor": 5000,
    "currency": "USD"
  },
  "action_hash": "sha256:...",
  "registry_sig": "hmac:..."
}

Rate Limiting

Refunds are evaluated through POST /api/verify/policy/finance.payment.refund.v1,
which is on the policy verification tier: 100,000 requests a minute in preview and
200,000 in production, from POLICY_RATE_LIMIT_PER_MINUTE. Counted per client,
per route.

There is no per-agent-per-hour counter and no burst allowance; earlier versions of
this page described both, and neither exists in the limiter. Per-agent refund
volume is bounded by the passport's own daily cap
(limits.payments.refund.currency_limits[currency].daily_cap), which is a money
limit rather than a request limit and is enforced by the policy, not the limiter.
See docs/rate-limiting.md.

Caching

  • Policy Cache: 60 seconds TTL
  • Verification Cache: 60 seconds TTL
  • Rate Limit Cache: 1 minute TTL

Security Considerations

  1. Idempotency Keys: Must be unique per agent/order combination
  2. Amount Validation: All amounts validated for precision and bounds
  3. Currency Support: Only supported currencies allowed
  4. Region Validation: Geographic restrictions enforced
  5. Verifiable Attestation: All decisions cryptographically signed
  6. Rate Limiting: DoS protection implemented
  7. Cross-Currency: Explicitly denied in v1

Migration from v0

If migrating from a previous version:

  1. Update SDK to latest version
  2. Add required fields to context
  3. Implement idempotency key generation
  4. Update error handling for new error codes
  5. Test with new assurance level requirements

Support

For questions or issues: