# CDK Bot API — Digital Products for AI Agents

**Base URL:** `https://api.cdk.bot`

Agent-native API for purchasing digital products — game keys, gift cards, and more. Pay with USDC on Base chain (instant). No registration required.

## MCP Server (Recommended for AI Agents)

**URL:** `https://mcp.cdk.bot/mcp`
**Transport:** Streamable HTTP

Add this URL to Claude Desktop, ChatGPT, Cursor, or any MCP-compatible client. 8 tools available: `search_games`, `get_game_details`, `browse_filters`, `get_price_quote`, `confirm_purchase`, `check_order`, `request_refund`, `submit_review`.

## Quick Start (REST)

One-shot (wallet-native agents) — one call, gasless:
```
1. GET /games/match?title=...                  → find the product
2. POST /purchase {"game_id":"..."}  + X-PAYMENT header (signed EIP-3009 "exact" for the 402 accepts[])
                                               → facilitator settles → product key (no tx_hash/quote_id)
```

Two-step (any agent) — send USDC yourself:
```
1. GET /games?q=battlefield&platform=Steam     → browse catalog
2. POST /purchase {"game_id": "..."}           → get payment details + accepts[] (HTTP 402)
3. Send USDC on Base                           → pay
4. POST /purchase {"game_id":"...","tx_hash":"0x...","quote_id":"..."} → receive product key
```

## Discovery & Docs

| Endpoint | Description |
|----------|-------------|
| `GET /.well-known/agent.json` | Agent discovery manifest — capabilities, endpoints, policies |
| `GET /openapi.json` | OpenAPI 3.1 specification (machine-readable) |
| `GET /docs` | Interactive Swagger UI |
| `GET /docs/markdown` | This document (plain text, agent-friendly) |
| `GET /llms.txt` | LLMs.txt discovery file |
| `GET /health` | Service health, trust stats, rate limits |

## Authentication

**None required.** All endpoints are public. No API keys, no tokens, no registration.

## Payment Methods

- **USDC on Base chain (x402).** Two ways to pay:
  - **One-shot (wallet-native agents):** POST /purchase with an `X-PAYMENT` header — a base64 EIP-3009 "exact" authorization signed for the accepts[] in the 402. A facilitator verifies + settles it on-chain (gasless — no ETH needed) and delivers the key in the same response. No tx_hash/quote_id.
  - **Two-step (any agent):** POST /purchase → 402 → send the USDC transfer yourself → POST again with tx_hash + quote_id. Onchain-verified after block confirmations.

## Security

- **quote_id required:** `quote_id` (from the 402 response) must be included with `tx_hash` in the payment step. It acts as a purchase session token — prevents front-running (an attacker seeing your tx_hash onchain cannot complete the purchase without your quote_id).
- **Wallet binding:** Sender wallet address is extracted from the onchain ERC-20 Transfer event and permanently bound to the order. Only that wallet can retrieve the key or request a refund.
- **Double-spend protection:** `tx_hash` has a unique constraint — same transaction cannot be used for two orders.

## Endpoints

### Browse

#### `GET /games` — Search products

Query params: `q` (search), `platform`, `device`, `region`, `language` (ISO 639-1), `product_type` (game|dlc|gift_card), `page` (default 1), `limit` (default 20, max 100).

Response: `{ games: [Game], total, page, limit, search_hint? }`

Game object: `{ id, name, product_type, platform, device, regions[], region_description, price_usdc, in_stock, languages[], language_coverage, language_note, updated_at }`

#### `GET /games/match` — Smart match (best result)

Query params: `title` (required), `platform`, `device`, `region`, `language`.

Response: `{ best_match: Game|null, alternatives: Game[], available_platforms[], available_devices[], available_regions[], explanation, total_variants }`

#### `GET /games/{id}` — Product details

Response: Game object + `activation_details: { activation_url, activation_steps }` + `avg_delivery_time_seconds`.

Returns 404 if product not found or out of stock.

#### `GET /platforms` — List platforms

Response: `{ platforms: [{ id, name }] }`

#### `GET /regions` — List regions

Response: `{ regions: [{ id, name }] }`

#### `GET /devices` — List devices

Response: `{ devices: [{ id, name }] }`

#### `GET /languages` — List languages

Response: `{ languages: [{ code, product_count }] }`

#### `GET /product-types` — List product types

Response: `{ product_types: [{ id, name, count }] }`

Types: `game` (base games + editions), `dlc` (downloadable content, requires base game), `gift_card` (gift cards, prepaid codes, subscriptions, in-game currency).

### Purchase

#### `POST /purchase` — Buy a product key (x402)

**One-shot (wallet-native agents):** POST `{ "game_id": "uuid" }` with an `X-PAYMENT` header — a base64 `{x402Version,scheme:"exact",network,payload:{signature,authorization}}` where `authorization` is an EIP-3009 `transferWithAuthorization` signed for the `accepts[0]` returned in the 402. A facilitator settles it gaslessly and the **200 OK** order (with `key_code`) is returned in one call — no `tx_hash`/`quote_id`.

**Two-step (any agent):**

**Step 1** — Request payment details:
```json
POST /purchase
{ "game_id": "uuid-here" }
```
Response: **402 Payment Required**
```json
{
  "payment": {
    "amount": "29.99",
    "currency": "USDC",
    "chain": "base",
    "address": "0x0b321A0F...",
    "quote_id": "uuid",
    "price_valid_for_seconds": 300
  }
}
```

**Step 2** — Submit payment proof:
```json
POST /purchase
{ "game_id": "uuid", "tx_hash": "0x...", "quote_id": "uuid" }
```
Response: **200 OK**
```json
{
  "id": "order-uuid",
  "key_code": "XXXXX-XXXXX-XXXXX",
  "status": "completed",
  "dispute_deadline": "2026-02-28T12:00:00.000Z"
}
```

If `status: "pending"`: key not yet delivered, poll `GET /orders/{id}` (includes `retry_after` and `estimated_delivery`).

Error codes: 404 (product not found), 409 (duplicate tx_hash / out of stock), 410 (quote expired — includes fresh quote), 422 (payment verification failed — includes `diagnosis` with code and hint), 500 (fulfillment failed — auto-refund created).

### Orders

#### `GET /orders/{id}?wallet_address=0x...` — Order status

`wallet_address` required to see `key_code`. Without it, key is hidden.

Response includes: `id, game_id, wallet_address, tx_hash, key_code, status, created_at, dispute_deadline, refund_id, refund_status, refund_tx_hash, refund_amount, sandbox`.

#### `GET /orders/{id}/receipt?wallet_address=0x...` — Purchase receipt

Available for completed production orders only. Returns seller info, line items, payment details, and receipt ID (= tx_hash as onchain proof).

### Refunds

#### `POST /refund` — Submit refund claim

```json
{
  "order_id": "uuid",
  "wallet_address": "0x...",
  "reason": "key_invalid"
}
```

**Reasons and SLA:**

| Reason | Auto-approved | Resolution time |
|--------|---------------|----------------|
| `not_delivered` | Yes (instant) | Immediate |
| `key_invalid` | No | 24 hours |
| `key_already_redeemed` | No | 24 hours |
| `wrong_product` | No | 24 hours |

**Rules:** 30-day dispute window from purchase. One refund per order. Sandbox orders excluded.

Response: `{ id, order_id, reason, resolution, refund_tx_hash, refund_amount, created_at }`

Resolution lifecycle: `pending` → `approved` → `refunded` (with onchain `refund_tx_hash`) | `denied`

## Sandbox Mode

Test the full flow on Base Sepolia — no real money:

1. Get test USDC from [Circle Faucet](https://faucet.circle.com/)
2. Add `"chain": "base-sepolia"` to POST /purchase
3. Receive mock key: `SANDBOX-XXXXX-XXXXX-XXXXX`

## Rate Limits

| Endpoint group | Requests | Window |
|---------------|----------|--------|
| Browse (GET) | 60 | 60s |
| Purchase (POST) | 5 | 60s |
| Orders (GET) | 10 | 60s |
| Refund (POST) | 3 | 60s |

## Links

- OpenAPI Spec: [https://api.cdk.bot/openapi.json](https://api.cdk.bot/openapi.json)
- Agent Discovery: [https://api.cdk.bot/.well-known/agent.json](https://api.cdk.bot/.well-known/agent.json)
- Health Check: [https://api.cdk.bot/health](https://api.cdk.bot/health)
- Terms of Service: [https://api.cdk.bot/terms](https://api.cdk.bot/terms)
