> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trygravity.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent Purchases

> Report the purchases your agent completes with Stripe Link so Gravity can attribute them to Index recommendations and pay you for them

Report the purchases your agent completes with [Stripe Link](https://github.com/stripe/link-cli) so
Gravity can attribute them to Index recommendations and pay you. One server-to-server call per
purchase, like the advertiser [Conversions API](/advertisers/server-conversions.md).

## How it works

1. `/search` returns a recommendation with a `grclid`, and when the merchant accepts machine payments
   a `checkout` pointer the agent can pay directly.
2. Your agent pays the merchant with Link (or hands the user `click_url`), keeping the `grclid` with
   the spend request.
3. When the spend request completes, your backend POSTs it to `/purchases/report`, with the
   `grclid` and the buyer's hashed email.

Send every purchase your agent completes. Some will carry a `grclid` from an Index result; send the
ones that don't too. Gravity uses the `grclid` and the hashed email to tie each purchase back to the
recommendations it made and credits you for the ones it can.

## 1. Pay with Link

Link CLI (`@stripe/link-cli`) is a tool the agent calls. The user signs in once with
`link-cli auth login`; US and Canadian Link accounts only for now.

**MCP server**

Add to your MCP client config (`.mcp.json` or equivalent). Every CLI command becomes a tool.

```json theme={null}
{
  "mcpServers": {
    "link": { "command": "npx", "args": ["@stripe/link-cli", "--mcp"] }
  }
}
```

Or run `link-cli serve` and point your host at `http://127.0.0.1:54321/mcp`.

**CLI as a tool**

Shell out with `--format json`. For a result with `checkout.protocol == "mpp"`, one call runs the
whole flow:

```bash theme={null}
npx @stripe/link-cli mpp pay "$CHECKOUT_ENDPOINT" \
  --context "Buying <product> for <what the user asked for>. Recommended by Gravity Index, click <grclid>. The user asked for this and approves the price shown in the merchant's 402 challenge." \
  --format json
```

Without `--spend-request-id` the command probes the `402`, parses the challenge, creates the
spend request, waits for the user's approval, pays, and prints the spend request plus the
merchant response. `--context` is required on that path and must be at least 100 characters;
the user reads it on the approval screen. In JSON mode the output carries `_next.poll_argv` /
`_next.pay_argv` for approval polling; pass those argv arrays straight to the CLI, never through
a shell.

For a merchant checkout that takes a card, create the spend request yourself and put the
`grclid` in its metadata so it comes back on retrieve:

```bash theme={null}
link-cli spend-request create --credential-type card \
  --merchant-name "$MERCHANT" --merchant-url "$MERCHANT_URL" \
  --amount "$AMOUNT_CENTS" --currency usd \
  --metadata "grclid:$GRCLID" --context "..." --request-approval --format json
```

For Stripe-hosted checkouts that accept a Link Pay Token, follow the
[Link Pay Token](https://github.com/stripe/link-cli#link-pay-token) section of the Link CLI
README and add the same `--metadata "grclid:$GRCLID"`.

From a `/search` result, `recommendation.checkout` is either `{protocol: "mpp", endpoint, network_id,
attribution}` (pay `endpoint` directly; it already carries `?grclid=…`) or `null` (no machine
checkout; use `click_url`). `checkout.network_id` is the merchant's Stripe `profile_…` id; `mpp pay`
reads it from the live `402` itself, so you only need it when creating the spend request by hand
(`link-cli mpp decode` extracts it from a raw `WWW-Authenticate` header). Link caps a spend request
at \$500.

## 2. Report the purchase

When the spend request reaches a terminal status, POST it from your backend. Send everything you
have: the merchant, the line items, the amounts, the ids Link gave you, the user's request that led
to the purchase, and the raw Link response if it's easy. Idempotent on `spend_request_id` for your
publisher, so retries are safe.

```bash theme={null}
curl -X POST https://index.trygravity.com/purchases/report \
  -H "X-API-Key: $GRAVITY_PUBLISHER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "spend_request_id": "lsrq_1UFjOgRqXMHXYStvozitEFZY",
    "status": "succeeded",
    "grclid": "2f1e0e0d-b6be-4621-b67a-be4ebfb921aa",
    "hashed_email": "b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514",
    "merchant_name": "Acme",
    "merchant_domain": "acme.com",
    "merchant_url": "https://acme.com/pro",
    "merchant_id": "acct_1AbC…",
    "line_items": [
      {"name": "Acme Pro (annual)", "sku": "acme-pro-1y", "category": "software",
       "quantity": 1, "unit_price": 4900, "url": "https://acme.com/pro"}
    ],
    "currency": "usd",
    "subtotal": 4900,
    "tax": 0,
    "amount": 4900,
    "credential_type": "shared_payment_token",
    "network_id": "profile_1AbC…",
    "transaction_id": "pi_3AbC…",
    "link_transaction_id": "ltxn_…",
    "link_session_id": "lsess_…",
    "purchased_at": "2026-10-22T18:04:11Z",
    "user_query": "project tracker for a 5 person team under $50",
    "agent_id": "shopper-v2",
    "session_id": "sess_8f1…",
    "metadata": {"order_id": "A-1001", "receipt_url": "https://acme.com/r/A-1001"},
    "link_response": {"id": "lsrq_1UFjOgRqXMHXYStvozitEFZY", "status": "succeeded", "...": "..."}
  }'
```

`X-API-Key` is the same publisher key you use for `/search`. Only `spend_request_id` and `status`
are required.

**spend\_request\_id** `string` *required*

Link `lsrq_…` id. Dedupe key.

**status** `string` *required*

Final spend-request status: `succeeded` for a completed purchase. `failed`, `denied`, `canceled`,
`expired`, and `approved` (approved but charge not observed) are accepted for reconciliation.

**Attribution**

**grclid** `string`

Send it whenever the purchase came from an Index result: `checkout.attribution.click_id_value`, or
the result's `grclid`. It must be a `grclid` issued to a search made with your key. Omit it when
there isn't one.

**hashed\_email** `string`

SHA-256 hex of the buyer's email, trimmed and lowercased (same as `hashed_email` on `/search`).
Include it on every report.

**Merchant**

**merchant\_name** `string`

As returned on the spend request.

**merchant\_domain** `string`

`acme.com`

**merchant\_url** `string`

Product or checkout URL.

**merchant\_id** `string`

Stripe account or merchant id if you have it.

**network\_id** `string`

Merchant `profile_…` from the spend request (MPP only).

**Items and amounts** (minor units, i.e. cents)

**line\_items** `object[]`

One per product: `name`, `sku`, `category`, `quantity`, `unit_price`, `url`. Extra keys are kept.

**currency** `string`

ISO 4217, three letters.

**subtotal** `integer`

**tax** `integer`

**amount** `integer`

Total charged.

**Ids and time**

**credential\_type** `string`

`shared_payment_token`, `card`, or `link_pay_token`.

**transaction\_id** `string`

Merchant or Stripe payment id (`pi_…`, order id).

**link\_transaction\_id** `string`

`ltxn_…`

**link\_session\_id** `string`

**purchased\_at** `string`

ISO 8601 timestamp of the charge.

**Context**

**user\_query** `string`

What the user asked for that led to this purchase.

**agent\_id** `string`

Which of your agents bought it.

**session\_id** `string`

Your conversation / session id.

**Anything else**

**metadata** `object`

Any JSON. Stored as sent, no schema.

**link\_response** `object`

The spend request object exactly as `link-cli` returned it (`--format json`).

Unknown top-level fields return `422`; put anything not listed above in `metadata`. The JSON
fields are capped at 64 KB each.

| Response | Meaning |
| - | - |
| `200 {"status": "recorded", "grclid": "…"}` | Stored. |
| `200 {"status": "duplicate", "grclid": "…"}` | Same `spend_request_id` already reported by you; nothing changed. |
| `400 unknown status` / `invalid grclid` | `status` not in the list above, or `grclid` is not a UUID. |
| `401` / `403` | Missing key, or a key that is invalid or blocked. |
| `404 unknown grclid` | A `grclid` was sent but was not issued to a search made with this key. |
| `413` | `metadata`, `line_items`, or `link_response` over 64 KB. |

Do not send Link access tokens, Shared Payment Tokens, card numbers, or the raw credential object.
Reporting to Gravity is separate from Link's own `link-cli report`, which goes to Stripe.

## Contact

`index@trygravity.ai` if your Link setup doesn't fit one of the shapes above.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.