# Sell your API to agents with Bags

> Machine-readable guide for coding agents. Goal: put a hosted
> pay-per-call (x402) paywall in front of your operator's existing API.
> Bags hosts the paywall and proxies paid calls — you add NO payment
> code, middleware, or proxy server to the operator's backend.

## Step 0 — Account and API key (human-in-the-loop)

Your operator needs a Bags account: sign up at https://www.getbags.app
and serve live x402 immediately; a lightweight payout check happens at
first withdrawal.

Then have the operator sign in to the BAGS Agent dashboard and mint an
API key from Settings → API keys (https://www.getbags.app/dashboard/settings). That
key is `$BAGS_KEY` in the examples below. Live keys on agentic accounts
are scoped to agentic surfaces: payment-link management and reading your
own transactions. The key is shown once at creation — store it
server-side. "No accounts, no API keys" applies to the *buyer* side —
agents paying the link never need either.

Live links can be created as soon as the account exists. Direct the human
to sign up; there is no unauthenticated programmatic signup endpoint.

## Step 1 — Create an agentic payment link

Dashboard route: Products → create a product (its price is the per-call
price) → Payment Links → Create New Link → select the product → leave
"Agentic Proxy (x402)" ON (default) → set Target API URL (the endpoint
Bags calls after payment; use "Test target URL" to probe it) → pick
upstream method (POST/GET), pricing scheme, and network(s) → save. The
success screen shows the paywalled URL with a copy button.

Or via API (no product needed; inline name + amount is enough):

```bash
curl -X POST https://www.getbags.app/api/payment-links \
  -H "Authorization: Bearer $BAGS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My API",
    "amount": 0.05,
    "network": "base",
    "isAgenticProxy": true,
    "targetUrl": "https://api.example.com/v1/endpoint",
    "x402Scheme": "exact"
  }'
```

Example 201 response (trimmed):

```json
{
  "status": "success",
  "data": {
    "id": "0f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0",
    "name": "My API",
    "amount": 0.05,
    "network": "base",
    "isAgenticProxy": true,
    "targetUrl": "https://api.example.com/v1/endpoint",
    "x402Scheme": "exact",
    "livemode": false
  }
}
```

`data.id` is a bare UUID — prefix it with `pl-` yourself when building
the paywalled URL (Step 2). It is NOT returned with the prefix.

Rules:
- One link maps to exactly one targetUrl. No path passthrough — create
  one link per endpoint you want to sell.
- Query strings pass through (caller params are merged onto targetUrl).
- All standard HTTP methods are supported; bodies forwarded for
  POST/PUT/PATCH.
- Pricing schemes: "exact" (flat per call, default) or "upto" (link
  amount is a ceiling; your API declares actual usage per call in an
  `x-bags-settle-amount` response header, decimal USD; missing header
  settles the ceiling, "0" delivers free). "upto" is EVM-only.

## Step 2 — The paywalled URL

```
https://www.getbags.app/api/agent/link/pl-{paymentLinkId}
```

Unpaid requests answer HTTP 402 with x402 payment instructions in the
`PAYMENT-REQUIRED` header. Publish this URL in the operator's site,
API docs, and agent manifests. After its first live settlement the link
is also listed automatically in the Coinbase CDP Bazaar catalog.

## Step 3 — Protect the upstream API from paywall bypass

Every proxied request carries an HMAC so the upstream can reject direct
calls. Implement verification in the operator's API:

Headers on each proxied request:
- `x-bags-proxy-signature`: `t=<unix-seconds>,v1=<hex hmac>`
- `x-bags-proxy-timestamp`: unix seconds (same value as `t`)

Signature = HMAC-SHA256(secret) over:

```
{timestamp}.{METHOD}.{pathWithQuery}.{sha256HexOfRawBody}
```

- METHOD uppercase; pathWithQuery is the targetUrl's path + query as
  Bags calls it (after query merge); empty body hashes the empty string.

Fetch the per-merchant secret once and store it server-side:

```bash
curl https://www.getbags.app/api/merchant/proxy-signing-secret \
  -H "Authorization: Bearer $BAGS_KEY"
```

Verification: read raw body bytes before parsing, recompute the HMAC,
compare constant-time, reject timestamps older than ~300 s, and reject
unsigned requests — but keep accepting the operator's existing API-key
auth so current customers are unaffected.

Worked example (Node/Express — adapt the same logic to any stack; the
critical part everywhere is hashing the RAW body bytes, before any JSON
parsing or body-parser middleware touches them):

```js
const crypto = require('crypto')

// express.raw() preserves the exact bytes Bags signed.
app.use('/v1/endpoint', express.raw({ type: '*/*' }), (req, res, next) => {
  // Keep existing customers working: API-key auth still passes.
  if (isValidApiKey(req.get('authorization'))) return next()

  const header = req.get('x-bags-proxy-signature') || ''
  const { t, v1 } = Object.fromEntries(
    header.split(',').map((p) => p.split('=').map((s) => s.trim()))
  )
  if (!t || !v1) return res.status(401).json({ error: 'unsigned' })
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300)
    return res.status(401).json({ error: 'stale timestamp' })

  const pathWithQuery = req.originalUrl // e.g. "/v1/endpoint?q=x"
  const bodyHash = crypto.createHash('sha256')
    .update(req.body?.length ? req.body : '')
    .digest('hex')
  const expected = crypto.createHmac('sha256', process.env.BAGS_PROXY_SECRET)
    .update(`${t}.${req.method.toUpperCase()}.${pathWithQuery}.${bodyHash}`)
    .digest('hex')

  if (v1.length !== expected.length ||
      !crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)))
    return res.status(401).json({ error: 'bad signature' })
  next()
})
```

Note: only `content-type`, `accept`, `accept-language`,
`user-agent`, and `x-bags-*` request headers are forwarded upstream.
`Authorization` and `Cookie` never are.

## Step 4 — Verify end-to-end

1. Create a low-value live link on Base.
2. `curl` the paywalled URL — expect HTTP 402 with payment metadata.
3. Pay with any x402 client funded via https://faucet.circle.com and
   confirm the call reaches the upstream API signed, and the payment
   appears in the dashboard.

Settlement semantics: payment is verified first, the call is proxied,
and funds settle only when the upstream responds with HTTP < 400.
4xx/5xx/timeout responses are never charged and the same payment
authorization is retryable. The upstream must return response headers
within 30 seconds (bodies may stream indefinitely).

## Step 5 — Publish the URL so agents find it

Bags lists each agentic link in the Coinbase CDP Bazaar catalog
automatically after its first live settlement, but the operator's own
surfaces matter just as much. Add the paywalled URL to whichever of
these exist in the operator's project (create `llms.txt` if missing):

- `llms.txt` / `llms-full.txt` — one line, e.g.:
  `- [My API (x402 pay-per-call)](https://www.getbags.app/api/agent/link/pl-{id}): $0.05 per request, USDC`
- `/.well-known/agents.md` or an agent-facing README section
- The operator's API docs and OpenAPI description
- MCP tool descriptions, agent-skill manifests, or plugin listings

State the price, that it speaks x402, and that no account or API key is
needed to pay it.

## References

- Provider docs: https://docs.getbags.app/docs/providers/sell-to-agents
- API auth guide: https://www.getbags.app/auth.md
- OpenAPI: https://www.getbags.app/api/openapi.json
