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
- An InsumerAPI key (free tier includes 10 credits). Get one here.
- A merchant ID. Browse available merchants with
GET /v1/merchantsor create your own. - A wallet address to verify. The endpoint verifies on-chain token holdings without exposing actual amounts.
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:
discounts.applied[0].coupon.percent_offis the discount percentage your agent should apply.verification.codeis theINSR-XXXXXcode to present at checkout (the same value is carried asapplied[0].id).discounts.applied[0].endis the code expiry (30 minutes from creation).verification.sigis the ECDSA P-256 signature proving authenticity.verification.breakdownshows which tokens matched and which tier each triggered.
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:
- 400 : Invalid wallet format, missing merchantId
- 401 : Missing or invalid API key
- 402 : Merchant has no credits remaining
- 404 : Merchant not found
- 429 : Daily rate limit exceeded (check
X-RateLimit-Remainingheader)
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 KeyGet every post by email
The Inevitable series and builder notes on condition-based access. Free, one or two posts a week.