OpenAI's Agentic Commerce Protocol (ACP) defines how AI agents request and apply discounts during checkout. This guide walks through integrating the InsumerAPI ACP discount endpoint, from your first API call to parsing coupon objects and validating codes at checkout.

What You Need

Step 1: Call the ACP Endpoint

The endpoint is POST /v1/acp/discount. Send a wallet address and merchant ID. InsumerAPI acts as an attestation authority: it verifies token holdings on-chain and returns a signed discount in ACP format.

curl:

curl -X POST \
  https://api.insumermodel.com/v1/acp/discount \
  -H "X-API-Key: insr_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": "demo-coffee-shop",
    "wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
  }'

demo-coffee-shop is InsumerAPI's demo store with USDC tiers configured, so the request runs as written; substitute a merchant ID you created through the merchant onboarding flow for your own store. A code that carries a discount costs 1 credit from the API key that owns the store, so codes from the demo store cost you nothing. The 8-minute setup guide walks through creating one.

Python:

import requests

BASE = "https://api.insumermodel.com"
KEY  = "insr_live_YOUR_KEY_HERE"

resp = requests.post(
    f"{BASE}/v1/acp/discount",
    headers={"X-API-Key": KEY, "Content-Type": "application/json"},
    json={
        "merchantId": "demo-coffee-shop",
        "wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
        "subtotal": 12000,
    },
)
data = resp.json()
print(data)

Node.js:

const BASE = "https://api.insumermodel.com";
const KEY  = "insr_live_YOUR_KEY_HERE";

const resp = await fetch(`${BASE}/v1/acp/discount`, {
  method: "POST",
  headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    merchantId: "demo-coffee-shop",
    wallet: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    subtotal: 12000,
  }),
});
const data = await resp.json();
console.log(data);

Step 2: Parse the ACP Response

A successful response returns the discount wrapped in ACP coupon objects. Here is the full shape:

{
  "ok": true,
  "data": {
    "protocol": "acp",
    "version": "2026-04-17",
    "discounts": {
      "codes": [],
      "applied": [
        {
          "id": "INSR-A7K3M",
          "coupon": {
            "id": "insumer-insr-a7k3m",
            "name": "Token Holder Discount (15% off)",
            "percent_off": 15
          },
          "amount": 1800,
          "automatic": true,
          "method": "across",
          "priority": 10,
          "start": "2026-02-27T12:00:00.000Z",
          "end": "2026-02-27T12:30:00.000Z"
        }
      ],
      "rejected": []
    },
    "verification": {
      "code": "INSR-A7K3M",
      "expiresAt": "2026-02-27T12:30:00.000Z",
      "breakdown": [
        { "symbol": "USDC", "tier": "Holder", "discount": 10 },
        { "symbol": "UNI", "tier": "Holder", "discount": 5 }
      ],
      "sig": "MEYCIQDx...base64...",
      "kid": "insumer-attest-v1"
    }
  },
  "meta": {
    "creditsCharged": 1,
    "creditsRemaining": 94
  }
}

Key fields to extract:

If the wallet does not qualify, applied is simply empty. rejected lists discount codes the caller submitted that could not be applied, and this endpoint receives no codes, so it stays empty too:

"discounts": {
  "codes": [],
  "applied": [],
  "rejected": []
}

The discount here is granted by what the wallet holds rather than by a code anyone entered, which is what ACP calls an automatic discount: automatic is true and the entry carries no code field. The INSR-XXXXX value is a redemption code this API mints, returned on applied[].id and in verification.code, and validated at GET /v1/codes/{code}.

Step 3: Parse Coupon Objects in Your Agent

Here is how an agent extracts and applies the discount in Python:

result = resp.json()

if not result["ok"]:
    print(f"Error: {result['error']['message']}")
else:
    discounts = result["data"]["discounts"]

    if discounts["applied"]:
        coupon = discounts["applied"][0]["coupon"]
        code   = result["data"]["verification"]["code"]
        expiry = discounts["applied"][0]["end"]

        print(f"Discount: {coupon['percent_off']}% off")
        print(f"Code: {code}")
        print(f"Valid until: {expiry}")
        print(f"Coupon name: {coupon['name']}")

        # Apply to cart
        cart_total = 5000  # $50.00 in cents
        discount_amount = cart_total * coupon["percent_off"] // 100
        final_total = cart_total - discount_amount
        print(f"Cart: ${cart_total/100:.2f} -> ${final_total/100:.2f}")
    else:
        # An empty applied array means the wallet did not qualify
        print("Not eligible: no configured tokens matched this wallet")

Step 4: Per-Item Allocations (Optional)

If your agent manages a multi-item cart, pass the items array. The endpoint calculates per-item discount allocations automatically:

resp = requests.post(
    f"{BASE}/v1/acp/discount",
    headers={"X-API-Key": KEY, "Content-Type": "application/json"},
    json={
        "merchantId": "demo-coffee-shop",
        "wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
        "items": [
            {"path": "cart/espresso", "amount": 450},
            {"path": "cart/croissant", "amount": 350},
        ],
    },
)

The response adds amount and allocations to the applied discount:

"applied": [{
  "id": "INSR-A7K3M",
  "coupon": {
    "id": "insumer-insr-a7k3m",
    "name": "Token Holder Discount (15% off)",
    "percent_off": 15
  },
  "amount": 120,
  "allocations": [
    {"path": "cart/espresso", "amount": 68},
    {"path": "cart/croissant", "amount": 52}
  ],
  "automatic": true,
  "method": "across",
  "priority": 10,
  "start": "2026-02-27T12:00:00.000Z",
  "end": "2026-02-27T12:30:00.000Z"
}]

The amount field is the total discount in the same unit as item amounts. The allocations array distributes it proportionally across items.

Step 5: Validate the Code at Checkout

When the customer presents the code, your merchant backend validates it with a simple GET request. No API key required:

GET /v1/codes/INSR-A7K3M

Valid code response:

{
  "ok": true,
  "data": {
    "valid": true,
    "code": "INSR-A7K3M",
    "merchantId": "demo-coffee-shop",
    "discountPercent": 15,
    "expiresAt": "2026-02-27T12:30:00.000Z",
    "createdAt": "2026-02-27T12:00:00.000Z"
  }
}

Expired code response:

{
  "ok": true,
  "data": {
    "valid": false,
    "code": "INSR-A7K3M",
    "reason": "expired"
  }
}

The three possible rejection reasons are expired, already_used, and not_found.

Step 6: Verify the Signature (Optional)

For high-value transactions, confirm the discount was issued by InsumerAPI and not tampered with. The verification object carries sig, an ECDSA P-256 signature, alongside kid: insumer-attest-v1. The signing key is public: fetch the JWKS at https://insumermodel.com/.well-known/jwks.json (also served at GET /v1/jwks, RFC 7517 format, 24-hour cache) and select the entry whose kid matches the response. One scope note: the insumer-verify package covers /v1/attest and /v1/trust envelopes, and commerce discount responses sit outside its input surface. The practical integrity check for a discount code is the one your merchant backend already runs at redemption: GET /v1/codes/{code} returns the authoritative validity, percentage, and expiry directly from the issuer.

Error Handling

All errors follow the same envelope:

{
  "ok": false,
  "error": { "code": 400, "message": "Invalid EVM wallet address" },
  "meta": { "version": "1.0", "timestamp": "..." }
}

Common error codes:

Full Working Example

Here is the complete flow in one Python script:

import requests

BASE = "https://api.insumermodel.com"
KEY  = "insr_live_YOUR_KEY_HERE"

# 1. Check discount eligibility via ACP
acp = requests.post(
    f"{BASE}/v1/acp/discount",
    headers={"X-API-Key": KEY, "Content-Type": "application/json"},
    json={
        "merchantId": "demo-coffee-shop",
        "wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
        "items": [
            {"path": "cart/latte", "amount": 550},
            {"path": "cart/muffin", "amount": 400},
        ],
    },
).json()

if acp["ok"] and acp["data"]["discounts"]["applied"]:
    code = acp["data"]["verification"]["code"]
    pct  = acp["data"]["discounts"]["applied"][0]["coupon"]["percent_off"]
    print(f"Eligible: {pct}% off, code {code}")

    # 2. At checkout, validate the code (no key needed)
    check = requests.get(f"{BASE}/v1/codes/{code}").json()

    if check["data"]["valid"]:
        print(f"Code valid. Apply {check['data']['discountPercent']}% off.")
    else:
        print(f"Code invalid: {check['data']['reason']}")
else:
    print("Wallet does not qualify for a discount.")

A forkable reference repository with all examples is available at acp-ucp-onchain-eligibility-example on GitHub. For the merchant setup side, see how to get token-gated discounts running in 8 minutes. For how on-chain verification powers broader eligibility credentials, see our analysis of the discount verification landscape.

Get your free API key

InsumerAPI includes 10 free verification credits. Start building with ACP today.

Get API Key

Get every post by email

The Inevitable series and builder notes on condition-based access. Free, one or two posts a week.