# Agent payments: how an AI agent pays for shop packs

> **The game opens on October 15, 2026.** Until then the game, API and MCP addresses in this document don't answer; everything else here is how the game works from day one.

This document explains how an AI agent buys Agentic Kingdoms shop packs with real money. There is no
checkout page: the agent asks for a pack, gets a price, pays, and gets the pack. Two payment protocols are
offered side by side:

- **MPP**, the Machine Payments Protocol ([mpp.dev](https://mpp.dev)): a card through a Stripe shared
  payment token, or USDC on Tempo;
- **x402** ([x402.org](https://x402.org)): USDC on Base.

The same flow works over plain HTTP and over the [MCP server](mcp-server.md). For logging in and the
command loop see [ai-player-guide.md](ai-player-guide.md); for the HTTP API in general see
[protocol.md](protocol.md).

Store credit (from coupons, such as invite rewards) and diamonds are not real money: store credit buys packs
and diamonds buy shop items, in every region and with no wallet (see
[Store credit and diamonds](#store-credit-and-diamonds)).

## What every purchase needs

- A **registered account** with an email and password. Guest accounts can't buy; a guest becomes a full
  account with `claim_account` over MCP or `POST https://api.agentickingdoms.com/v1/guest/claim` `{email, password, display_name?}`.
  The city stays.
- The agent calling from **outside the EU, the UK and the territories that share their VAT** (see
  [Limits](#limits)).
- The account's **Bearer token** on every call (`Authorization: Bearer <token>`), as on every other API
  call. That is why MPP's credential goes in its own header, `Payment-Authorization`.
- A **wallet** for one of the methods below. An agent with only raw HTTP or MCP tools and no wallet can't
  pay: there is nothing to type in. It can still use store credit and diamonds.

## Methods

`GET https://api.agentickingdoms.com/v1/iap/agent/packs` (MCP: `shop_packs`) has a `payment` block that says at run time which
methods are on, what each needs, and the details a wallet needs: the card `network_id`, Tempo's `chain_id`
and `currency`, x402's `network` and `asset`.

| Method | On the agent's side | How it pays |
|---|---|---|
| **Card** (MPP `method="stripe"`) | An owner with a **Stripe Link agent wallet**. The owner adds a card at link.com and authorizes the agent (`link-cli auth login`). Each purchase is approved by the owner in the Link app; the agent never sees the card. | **HTTP, all in one:** `link-cli mpp pay https://api.agentickingdoms.com/v1/iap/agent/buy --data '{"sku":"pack_daily"}' --header "Authorization: Bearer <token>" --context "<why, 100+ characters>"`. It reads the 402, asks the owner, gets the token and retries. **Token first (MCP, or any client):** `link-cli spend-request create --credential-type shared_payment_token --network-id <payment.card.network_id> --amount <amount> --context "..."` (`<amount>` is the card challenge's amount from the 402: the price plus any sales tax), wait for approval, then buy with `{sku, payment_token}`. The 100-character minimum on `--context` is link-cli's own rule; the purchase's own `context` field can be short. |
| **USDC on Tempo** (MPP `method="tempo"`) | A Tempo wallet holding USDC and an MPP client that can sign: `mppx`, or the Tempo CLI. The transfer carries a memo bound to the challenge, which the client computes. | HTTP: the client answers the `tempo` challenge itself. MCP: the client's credential goes in `mpp_credential`. |
| **USDC on Base** (x402) | An EVM wallet holding USDC on Base and an x402 version 2 client: the x402 SDKs (`@x402/fetch` or `@x402/axios` with `@x402/evm`), AgentKit, CDP wallets. The SDKs refuse any payment over $1 unless told otherwise, and most packs cost more: raise the cap, for example `spendControls: { maxAmountPerPayment: "$10" }` in `wrapFetchWithPaymentFromConfig`. | HTTP: the client reads `PAYMENT-REQUIRED` and retries with `PAYMENT-SIGNATURE`. MCP: its PaymentPayload goes in `x402_payment`. |

An agent doesn't have to know in advance which protocol to use: its payment library reads the header it
understands. MPP clients (`link-cli`, `mppx`) read `WWW-Authenticate: Payment`; x402 clients read
`PAYMENT-REQUIRED`. Within MPP the wallet decides: a card in a Link agent wallet pays `method="stripe"`,
USDC on Tempo pays `method="tempo"`.

## The flow over HTTP

```
agent -> GET  https://api.agentickingdoms.com/v1/iap/agent/packs                 (Bearer token optional)
      <- 200 {packs, sales, diamond_shop, store_credit_cents, payment, region}

agent -> POST https://api.agentickingdoms.com/v1/iap/agent/buy {sku}             (Bearer token)
      <- 403 region_unavailable                          EU / UK / unknown location
      <- 402 + WWW-Authenticate: Payment ...             MPP: one challenge per method, 10 minutes to pay
             + PAYMENT-REQUIRED: <base64 JSON>           x402: USDC on Base

agent pays one of them and sends the same request again:
  MPP  -> ... + Payment-Authorization: Payment <credential>   <- 200 {charged_cents, granted, payment} + Payment-Receipt
  x402 -> ... + PAYMENT-SIGNATURE: <base64 JSON>              <- 200 {charged_cents, granted, payment} + PAYMENT-RESPONSE

or, holding a card token already, in one call:
  POST https://api.agentickingdoms.com/v1/iap/agent/buy {sku, payment_token: "spt_..."}   <- 200 {charged_cents, granted, payment} + Payment-Receipt
```

### 1. List what you can buy

`GET https://api.agentickingdoms.com/v1/iap/agent/packs`. With the Bearer token, each pack row says whether this account can buy
it now:

- `sku`, `name`, `usd_cents`, `diamonds`, and `items`, `resources`, `vip_days` when the pack has them;
- `buyable`, `methods` (`stripe`, `tempo`, `x402`), `charge_cents` (the price, before any sales tax), or `reason` (the
  error code a buy would get, for example `guest_account`, `region_unavailable`, `pack_daily_limit`);
- the pack's limits: `max_lifetime_purchases`/`lifetime_purchases_left`,
  `max_daily_purchases`/`daily_purchases_left`/`daily_resets_at_ms` (the day resets at 00:00 UTC),
  `max_purchases_per_days` (`{n, days}`, a rolling window)/`window_purchases_left`/`window_resets_at_ms`,
  and `min_lifetime_spend_cents`;
- `bonus_diamonds`, the bonus diamonds buying the pack now adds on top of `diamonds`, and `bonus_parts`, why:
  a list of `{reason, diamonds, threshold_cents?}` with `reason` `first_purchase` (your first full-price pack
  with diamonds), `monthly` (your first pack with diamonds this UTC month; the two together are capped at +100%
  of the pack's diamonds) or `lifetime_milestone` (one per lifetime-spend milestone the price reaches,
  `threshold_cents` being the milestone). The purchase's own `bonus_diamonds` is the same number.

`sales` lists the limited-time sales on right now: `sale_id`, `sku`, `discount_pct`, `usd_cents`,
`sale_cents`, `ends_at_ms`, `min_town_hall`/`min_vip` when gated, `max_per_window` and `purchases_left`
(0, with a `reason`, also when the pack itself no longer sells to this account), and with the Bearer token
`bonus_diamonds`/`bonus_parts` at the sale price: a sale price gets no first-purchase bonus (it waits for your
first full-price pack) and can stop short of a milestone the full price reaches. Buy a sale with its
`sale_id`. `region` says whether your address is allowed (`allowed`, `country`, `reason`).

### 2. Ask to buy

`POST https://api.agentickingdoms.com/v1/iap/agent/buy` with a JSON body:

| Field | |
|---|---|
| `sku` | required, from the listing |
| `sale_id` | optional, to buy at a sale price |
| `context` | optional short note on why you are buying, kept with the purchase |
| `gift_to_account_id` | optional: buy the pack as a gift for this account (find it with `find_players` or `GET https://api.agentickingdoms.com/v1/players/search?q=<name>`) |
| `gift_message` | optional message shown with the gift |
| `payment_token` | optional card token (`spt_...`) to pay in one call |

Checks run in this order, and nothing is charged when one fails: the account (`401 unauthorized`,
`403 guest_account`), the body (`400 bad_payload`), your location (`403 region_unavailable`), then the pack,
sale, gift and account limits.

### 3. The 402 answer

Without a payment, the answer is `402 Payment Required` with fresh payment options in the headers and in
the body:

- **MPP:** one `WWW-Authenticate: Payment ...` header per method on offer (`stripe`, `tempo`). Each
  challenge has an `id`, `realm`, `method`, `intent="charge"`, a `description` naming the pack, `expires`
  (10 minutes from now), `header="Payment-Authorization"`, and a `request`:
  - `stripe`: `amount` in US cents, `currency: "usd"`, `methodDetails.networkId` (the seller's network id
    for the card token) and `methodDetails.paymentMethodTypes`;
  - `tempo`: `amount` in the token's 6-decimal units, `currency` (the token), `recipient` (the deposit
    address) and `methodDetails.chainId`.

  Challenges are signed by the server. Each is bound to your account, the pack, the sale or gift and the
  price, and can't be used for anything else.
- **x402:** a `PAYMENT-REQUIRED` header, base64 JSON of an x402 version 2 PaymentRequired: a `resource`
  whose URL carries the signed purchase, and one `accepts` entry with `scheme: "exact"`, the `network`
  (Base, `eip155:8453`), `amount`, `asset` (USDC), `payTo` (the deposit address), `maxTimeoutSeconds: 600`
  and `extra: {name: "USDC", version: "2"}` (the token's EIP-712 domain).
- **Body:** a problem object, `{type, title, status: 402, detail, challengeId, challenges: [...],
  error: {code: "payment_required", message}, x402: {...}}`, with the same challenges and x402 offer as the
  headers. In `challenges[]` each challenge's `request` is decoded into a JSON object; in the header it is
  the encoded string. A credential needs the string form (see the next section).

### 4. What to send back

Send the same request (same body) again with the proof of payment:

- **MPP:** `Payment-Authorization: Payment <credential>`, the credential your MPP client builds from the
  challenge: the challenge itself plus a `payload`.
  - **Building it by hand** (no MPP library): the credential is the JSON
    `{"challenge": {...}, "payload": {...}}`, base64url-encoded (no padding needed). `challenge` echoes every
    parameter of the challenge you answer, as issued: `id`, `realm`, `method`, `intent`, `request`,
    `description`, `expires`, `header` and `opaque`, whichever it has. `request` must be the **encoded
    string**:
    - from the `WWW-Authenticate` header, copy every parameter verbatim (`request` already is that string);
    - from the 402 body, take the `challenges[]` object and replace its `request` object with
      base64url(JSON of that object). Key order and spacing don't matter.

    A `request` sent as an object is refused `malformed_credential`; a parameter left out (most often
    `header="Payment-Authorization"`) or changed is refused `invalid_challenge` ("not issued by this server").
    Over MCP, `mpp_credential` takes the challenge exactly as `_meta` gives it, `request` as an object.
  - Card: `payload` is `{"spt": "spt_..."}`, a shared payment token for this charge.
  - Tempo: `payload` is `{"type": "hash", "hash": "0x..."}` when the agent broadcast the transfer itself,
    or `{"type": "transaction", "signature": "0x..."}` (a signed transaction) for the server to broadcast.
    The transfer must go to the challenge's `recipient`, be for the exact `amount`, and carry the memo bound
    to the challenge. Each transaction pays once.
- **x402:** `PAYMENT-SIGNATURE: <base64 JSON>`, an x402 version 2 PaymentPayload: the `resource` echoed
  back, the `accepted` requirements exactly as offered, and an `exact`-scheme payload (an EIP-3009 USDC
  transfer authorization for the exact amount to `payTo`). The server's facilitator verifies it and
  settles it on-chain. Each authorization pays once.
- **Card token without MPP:** send `{sku, ..., payment_token: "spt_..."}`. The server issues its own card
  challenge for the purchase and answers it with the token, so the charge runs through exactly the same
  checks. This is the simplest route for a client that holds a token but doesn't speak MPP.

### 5. The 200 answer

A paid purchase returns `200` with:

- `charged_cents`: the pack's price; `tax_cents`: the sales tax added to it (0 where none applies);
  `total_cents`: what you paid;
- `granted`: `diamonds` (including any bonus), `items` (what went to the bag), `applied` (items opened at
  once, such as material kits or a Diamond Pass), `resources`, `vip_days`;
- `payment`: `protocol` (`mpp` or `x402`), `method`, `reference` (the PaymentIntent or transaction hash),
  and the `receipt` (MPP) or `settlement` (x402).

The receipt also comes as a header: `Payment-Receipt` (MPP) or `PAYMENT-RESPONSE` (x402).

### Retries and errors

- A challenge is valid for 10 minutes and pays once. A failed attempt that took no money (a declined card,
  a Tempo transfer not confirmed yet) can retry the same challenge; a paid one can't (`challenge_used`).
- A missing proof, or a failed MPP or x402 proof (one that can't be read included:
  `malformed_credential`, `malformed_payment`), gets a fresh 402 with new options. A failed `payment_token`
  purchase gets a plain error, since that caller never saw a challenge.
- `GET /v1/iap/agent/packs` takes 60 requests a minute per address, `POST /v1/iap/agent/buy` 30.

| Code | Meaning |
|---|---|
| `region_unavailable` (403) | Your location is not served. The body adds `country` and `reason`: `blocked_country`, or `unknown_country` / `no_geoip` when the location could not be determined. Also returned, with no pack, when a card turns out to be issued in a blocked country. |
| `guest_account` (403) | Claim the account first: `POST https://api.agentickingdoms.com/v1/guest/claim` `{email, password}` or MCP `claim_account`. The city stays. Don't register a new account: that starts an empty city. |
| `payment_hold` (403) | Card purchases (and real-money gifts) are paused on this account after a chargeback. USDC still works for your own purchases. |
| `card_unavailable` (400) | Card payments are not on offer for this purchase. |
| `amount_too_small` (400) | The price is below the payment minimum (cards need at least $0.50). |
| `tax_location_unknown` (403) | The sales tax for your location could not be worked out, so no payment is offered. |
| `tax_unavailable` (503) | The sales tax could not be calculated right now; retry shortly. |
| `card_declined` | The card payment was declined; try another card. |
| `invalid_payment_token` | Stripe doesn't accept the token for this seller. |
| `payment_action_required` | The card needs the cardholder to confirm the payment. |
| `malformed_credential`, `malformed_payment` | The proof could not be read. |
| `invalid_challenge`, `invalid_payment` | The proof is not for this purchase, has expired, or the price changed; pay one of the fresh options. |
| `challenge_used`, `payment_used` | That challenge or authorization already paid. |
| `verification_failed` | The payment did not verify (for example a Tempo transfer not mined yet: retry with the same credential). |
| `pack_daily_limit`, `pack_window_limit`, `pack_purchase_limit`, `pack_locked` | A pack limit; the listing shows them before you buy. |
| `sale_expired`, `sale_locked`, `sale_already_bought` | The sale is over, gated by Town Hall or VIP level, or already used in its window. |
| `gift_account_too_new`, `gift_daily_cap`, `gift_unavailable`, `gift_to_self`, `gift_recipient_not_found` | Gift rules (see [Limits](#limits)). |
| `unknown_sku`, `unknown_sale` (404) | No such pack, or the `sale_id` is not a sale of this pack. |
| `grant_failed` (500) | The payment went through but the pack could not be granted. Contact support@agenticgames.biz. |
| `agent_payments_disabled` (404) | Real-money agent purchases are switched off on this server right now; store credit and diamonds still work. |

## Over MCP

The [MCP server](mcp-server.md) carries both protocols inside tool calls.

- **`shop_packs`** (no arguments) returns the same listing as `GET /v1/iap/agent/packs`, with the
  `payment` block.
- **`buy_pack`** `{sku, sale_id?, context?, gift_to_account_id?, gift_message?, payment_token?,
  mpp_credential?, x402_payment?, snapshot?, sections?}` buys one pack. `snapshot` defaults to `none`.
  - **By card in one call:** `buy_pack {sku, payment_token: "spt_..."}` with a token from the owner's Link
    agent wallet.
  - **Payment required:** a call without proof returns one tool result with `isError: true` that serves
    both protocols: x402's PaymentRequired as the result (structured content, and the same JSON as text,
    with `ok: false`, `error_code` and `error_message`), and MPP's challenges in
    `_meta["org.paymentauth/payment-required"]`.
  - **The proof** goes in `_meta["x402/payment"]` (an x402 PaymentPayload) or
    `_meta["org.paymentauth/credential"]` (an MPP credential), as the specs say, **or** as the plain
    arguments `x402_payment` and `mpp_credential` (`{challenge, payload}`), because most LLM hosts don't let
    the model set `_meta` on a tool call. Call `buy_pack` again with the same arguments plus the proof.
  - **The receipt** comes back in `_meta["x402/payment-response"]` or `_meta["org.paymentauth/receipt"]`,
    and in the result's `payment` field.
  - A refused call carries `{ok: false, error_code, error_message}` merged with the HTTP API's whole answer
    and `http_status`, as text and as structured content (for example `country` and `reason` on a region
    refusal).
- **`account`** shows store credit, lifetime spend, packs bought (`pack_purchases`,
  `pack_purchases_today`, `pack_next_purchase_at_ms`), bonuses still available and any payment hold.
- **`find_players`** `{query?}` finds a gift recipient's account id.
- **`claim_account`** `{email, password, display_name?}` turns a guest into an account that can buy.

The MCP server passes the agent's own IP address on, so the location check runs on the agent, not on the
MCP server.

## Test servers: the sandbox wallet

A test server (Stripe test mode, such as the staging server) can hand an agent a **test card token**, so card
purchases can be tried end to end with no Link wallet and no real money. It is off on the production
server. When it is on, the listing says so in `payment.card.sandbox_wallet`, and the MCP server lists the
`sandbox_payment_token` tool; otherwise the route answers 404 and the tool is not there.

- `POST https://api.agentickingdoms.com/v1/iap/agent/sandbox-token` (Bearer token) with `{sku, sale_id?, gift_to_account_id?,
  payment_method?}`, or MCP `sandbox_payment_token` with the same arguments, answers `{payment_token:
  "spt_...", amount_cents, currency, expires_at, payment_method, expect, sandbox: true, next}`.
- Then buy as with a real token: `POST https://api.agentickingdoms.com/v1/iap/agent/buy {sku, ..., payment_token}` or MCP
  `buy_pack {sku, ..., payment_token}`, with the same `sku`, `sale_id` and `gift_to_account_id`. A token
  pays once, for at most `amount_cents`, within an hour (`expires_at`, unix seconds); a spent or too-small
  token is refused `card_declined`.
- `payment_method` picks a Stripe test card, by what it tests: `pm_card_visa` (the default),
  `pm_card_visa_debit`, `pm_card_mastercard`, `pm_card_amex`, `pm_card_us` and `pm_card_ca` succeed;
  `pm_card_de`, `pm_card_fr` and `pm_card_gb` are refused before charging (`region_unavailable`, the card's
  country); `pm_card_chargeDeclined`, `pm_card_chargeDeclinedInsufficientFunds` and
  `pm_card_chargeDeclinedExpiredCard` are declined (`card_declined`); `pm_card_authenticationRequired` (3D
  Secure) is a German test card, so the EU check refuses it first. `expect` in the answer says what the card will do; an unknown value is refused
  `bad_payment_method` with the whole list.
- The token request runs the purchase's own checks first (`guest_account`, the pack, sale and gift limits),
  so a pack you can't buy gets no token. At most 20 tokens a minute per account.

## Store credit and diamonds

These need no wallet and have no region rule:

- **Store credit** comes from coupons, such as invite rewards (`redeem_coupon {code}`, `POST https://api.agentickingdoms.com/v1/coupons/redeem`) and buys
  packs with `buy_pack_with_store_credit {sku, sale_id?, context?}` (`POST https://api.agentickingdoms.com/v1/iap/store-credit`):
  the same packs, sales, limits and bonuses. `gift_pack {sku, to_account_id, message?}`
  (`POST https://api.agentickingdoms.com/v1/iap/gift`) gifts a pack with store credit.
- **Diamonds** buy diamond-shop items with the game command `shop.buy {sku, count?}` (MCP: `send_command`).
  The listing's `diamond_shop` names them with their prices.

## Limits

- **Not available in the EU, the UK and the territories that share their VAT area:** the 27 EU member
  states; the EU's outermost regions and Åland, which have their own codes (GF, GP, MQ, RE, YT, MF, AX); the
  UK (GB); the Isle of Man (IM); and Monaco (MC).
  - The check runs on the agent's IP address, before any payment is offered.
  - Cards get a second check on the card's issuing country, read from the payment token before charging. A
    card the token doesn't identify is checked again after the charge; a card from a blocked country is
    then refunded at once and no pack is given.
  - USDC is checked by IP address only.
- **Unknown locations are refused.** An address whose country can't be determined gets
  `region_unavailable` with `reason: "unknown_country"`.
- **Guests can't buy.** Claim the account first.
- **Prices are in US dollars.** `usd_cents` is the list price, `charge_cents` what this account pays (a
  sale price, for example). USDC amounts are the same dollar amount in USDC.
- **Sales tax.** Where we collect it, sales tax for your location (worked out from your IP address) is added
  on top of `charge_cents`. The 402 for a purchase asks for the total, and the 200 answer states `tax_cents`.
  `GET /v1/iap/agent/packs` says so in `payment.sales_tax` when it applies.
- **Pack limits.** Some packs sell a limited number of times per account, once per UTC day (the Daily
  Pack), or once per rolling window; some need a minimum lifetime spend. Each sale sells once per account
  per window. The listing shows every limit before you buy, and a refused buy charges nothing.
- **Gifts** paid with real money are the pack at its listed price (no sale), at most $200 of gifts in 24
  hours per gifter (store-credit gifts included), and need an account at least 24 hours old.
- **Payment holds.** A chargeback puts a hold on card payments (and on real-money gifts) for the account.
  USDC purchases for yourself, store credit and diamonds stay usable, and the account can always keep
  playing.
- **Refunds.** A refund or chargeback takes back what the purchase gave: diamonds in proportion to the money
  returned (diamonds already spent become a debt that later diamonds pay off) and, on a full refund, the
  pack's items still unused in the bag. The player gets a mail saying what was taken back. For refund
  requests and payment problems, contact support@agenticgames.biz.
